diff --git a/Gopkg.lock b/Gopkg.lock index c8ebe4cb53..e8ae57e3c2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -94,6 +94,14 @@ revision = "879f631812a30a580659e8035e7cda9994bb99ac" version = "v1.20.0" +[[projects]] + digest = "1:e92f5581902c345eb4ceffdcd4a854fb8f73cf436d47d837d1ec98ef1fe0a214" + name = "github.com/StackExchange/wmi" + packages = ["."] + pruneopts = "UT" + revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338" + version = "1.0.0" + [[projects]] digest = "1:8c3176921eb3a8c3b9cadd2bbb2e8e39f62a99ee2e0965074a857868cc18a10b" name = "github.com/aliyun/alibaba-cloud-sdk-go" @@ -533,6 +541,17 @@ revision = "07c9b44f60d7ffdfb7d8efe1ad539965737836dc" version = "v0.4.0" +[[projects]] + digest = "1:c96d16a4451e48e2c44b2c3531fd8ec9248d822637f1911a88959ca0bcae4a64" + name = "github.com/go-ole/go-ole" + packages = [ + ".", + "oleutil", + ] + pruneopts = "UT" + revision = "39dc8486bd0952279431257138bc428275b86797" + version = "v1.2.2" + [[projects]] digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" name = "github.com/go-sql-driver/mysql" @@ -1117,6 +1136,29 @@ pruneopts = "UT" revision = "49a4782e9908fe098c907022a1bd7519c79803d6" +[[projects]] + digest = "1:946db283b5540b64c60e7517b5afa54dc148b4672d69b38423a1c8d7fe24f3d1" + name = "github.com/shirou/gopsutil" + packages = [ + "cpu", + "host", + "internal/common", + "mem", + "net", + "process", + ] + pruneopts = "UT" + revision = "3ec50d2876a36047b2ca39f955ba88fb7a455e92" + version = "v2.18.10" + +[[projects]] + branch = "master" + digest = "1:99c6a6dab47067c9b898e8c8b13d130c6ab4ffbcc4b7cc6236c2cd0b1e344f5b" + name = "github.com/shirou/w32" + packages = ["."] + pruneopts = "UT" + revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b" + [[projects]] digest = "1:69b1cc331fca23d702bd72f860c6a647afd0aa9fcbc1d0659b1365e26546dd70" name = "github.com/sirupsen/logrus" @@ -1615,11 +1657,11 @@ [[projects]] branch = "master" - digest = "1:9e1972be6a76a8ef37aebcf94dedbe0508274464b5091baff4a8780583affebb" + digest = "1:9a92b34083e218c123d21a4b0d74228df6b186de9dcd6489e5b4cf68f4e90104" name = "yunion.io/x/jsonutils" packages = ["."] pruneopts = "UT" - revision = "c191216583f759c3d54a1d1d84c9832fdee68596" + revision = "c40afc81cccc4618168f722c64deba7cfbb1f8bd" [[projects]] branch = "master" @@ -1634,7 +1676,7 @@ [[projects]] branch = "master" - digest = "1:c6edf85623ec3a8c5e688dafbdc5bbde1d6057b9d0d9b78e6cb6dd84a52b23c0" + digest = "1:6277045f9c9a63d84e1aa48c7ad3c1f42ea6523bc54ea92e772d541c78e37404" name = "yunion.io/x/pkg" packages = [ "gotypes", @@ -1657,6 +1699,7 @@ "util/seclib", "util/secrules", "util/sets", + "util/signalutils", "util/stringutils", "util/timeutils", "util/trace", @@ -1667,23 +1710,23 @@ "utils", ] pruneopts = "UT" - revision = "9075712a1c3ff8c0bad1c413e18f0b7f2c18f29c" + revision = "e2e42205d868c26829b9322b40cdc48f24fed379" [[projects]] branch = "master" - digest = "1:82629c6aeecc1e6e3ea7108385aaac2bdcceeba8f8dc3175daca256602e3be71" + digest = "1:0e3c8da76b7b7ba0f67ef0737d361e117b65bd328bfbcb28fb030268df587ca9" name = "yunion.io/x/sqlchemy" packages = ["."] pruneopts = "UT" - revision = "c2445071ddf7b3f2a22ccdd3be684b67ac2f9008" + revision = "0b1ca973f3e5140dee7e773fc51cfc8c07ce0f69" [[projects]] branch = "master" - digest = "1:6ea9ac8f317e7dd79309bb1633642bc9d1822011c5175bd241552534d216ac6d" + digest = "1:cec4892868ae1c9fec0d2cace79d4c43e620345cc3c070d004dec09caf197208" name = "yunion.io/x/structarg" packages = ["."] pruneopts = "UT" - revision = "e4f0f5201b4acad185ebfb76e65905fe5ae7c1b1" + revision = "f39549642b91f03f0b46403f9856d4750e3e59eb" [solve-meta] analyzer-name = "dep" @@ -1759,6 +1802,11 @@ "github.com/nelsonken/cos-go-sdk-v5/cos", "github.com/pierrec/lz4", "github.com/serialx/hashring", + "github.com/shirou/gopsutil/cpu", + "github.com/shirou/gopsutil/host", + "github.com/shirou/gopsutil/mem", + "github.com/shirou/gopsutil/net", + "github.com/shirou/gopsutil/process", "github.com/stretchr/testify/assert", "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common", "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http", @@ -1774,6 +1822,9 @@ "github.com/vmware/govmomi/vim25/types", "go.etcd.io/etcd/clientv3", "golang.org/x/crypto/ssh", + "golang.org/x/net/bpf", + "golang.org/x/net/ipv4", + "golang.org/x/sys/unix", "gopkg.in/gin-gonic/gin.v1", "k8s.io/api/core/v1", "k8s.io/apimachinery/pkg/api/errors", @@ -1804,6 +1855,7 @@ "yunion.io/x/pkg/util/seclib", "yunion.io/x/pkg/util/secrules", "yunion.io/x/pkg/util/sets", + "yunion.io/x/pkg/util/signalutils", "yunion.io/x/pkg/util/stringutils", "yunion.io/x/pkg/util/timeutils", "yunion.io/x/pkg/util/trace", diff --git a/Gopkg.toml b/Gopkg.toml index a48c5e21c3..2a76ddc2e7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -195,3 +195,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/shirou/gopsutil" + version = "2.18.10" diff --git a/build/build.sh b/build/build.sh index 0cc7a2c028..68ede5c51e 100755 --- a/build/build.sh +++ b/build/build.sh @@ -128,7 +128,9 @@ getent passwd %{owner} >/dev/null || /usr/sbin/useradd -r -s /sbin/nologin -d %{ $(for b in $EXTRA_BINS; do echo /opt/yunion/bin/$b; done) " > $SPEC_FILE -find $ROOT/root/ -type f | sed -e "s:$ROOT/root::g" >> $SPEC_FILE +if [ -d $ROOT/root/ ]; then + find $ROOT/root/ -type f | sed -e "s:$ROOT/root::g" >> $SPEC_FILE +fi rpmbuild --define "_topdir $BUILDROOT" -bb $SPEC_FILE diff --git a/build/logger/root/etc/yunion/log.conf.sample b/build/logger/root/etc/yunion/log.conf.sample new file mode 100644 index 0000000000..36e1da4c13 --- /dev/null +++ b/build/logger/root/etc/yunion/log.conf.sample @@ -0,0 +1,9 @@ +region = 'LocalTest' +port = 9999 +auth_uri = 'http://10.16.22.51:35357/v2.0' +admin_user = 'logadmin' +admin_password = 'password0' +admin_tenant_name = 'system' +sql_connection = 'mysql+pymysql://yunionlogger:password0@10.16.22.51:3306/yunionlogger?charset=utf8' + +auto_sync_table = True diff --git a/build/logger/root/opt/yunion/share/logger/yunion-logger.service b/build/logger/root/opt/yunion/share/logger/yunion-logger.service new file mode 100644 index 0000000000..94dd82aebd --- /dev/null +++ b/build/logger/root/opt/yunion/share/logger/yunion-logger.service @@ -0,0 +1,17 @@ +[Unit] +Description=Yunion Cloud Log Service +Documentation=http://doc.yunionyun.com +After=network.target httpd.service + +[Service] +Type=simple +User=yunion +Group=yunion +ExecStart=/opt/yunion/bin/logger --config /etc/yunion/log.conf +WorkingDirectory=/opt/yunion +KillMode=process +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/build/logger/vars b/build/logger/vars new file mode 100644 index 0000000000..3c84520c43 --- /dev/null +++ b/build/logger/vars @@ -0,0 +1 @@ +DESCRIPTION="Yunion Cloud Log Service" diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CHANGE_FLAVOR b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CHANGE_FLAVOR new file mode 100644 index 0000000000..0c93926cd7 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CHANGE_FLAVOR @@ -0,0 +1 @@ +您的云主机{{ .name }}的套餐类型已经更改为 CPU:{{ index .flavor_info "cpu" }}核,内存:{{ index .flavor_info "memory" }}M,数据盘:{{ index .flavor_info "datadisk" }}G,带宽: {{ index .flavor_info "ebw" }}M。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CREATED b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CREATED new file mode 100644 index 0000000000..b2db433683 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CREATED @@ -0,0 +1 @@ +您的云主机{{ .name }}已经创建成功,服务器IP地址为{{ .ips }},{{ if .account }}初始帐号为{{ .account }},{{ end }}{{ if .keypair }}访问ssh密钥为{{ .keypair }},{{ end }}{{ if .password }}初始密码为{{ .password }},{{ end }}请使用{{ if .windows }}远程桌面连接器(RDC){{ else }}SSH{{ end }}或控制面板控制台访问云主机。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CREATED_ADMIN b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CREATED_ADMIN new file mode 100644 index 0000000000..b1a27e7f60 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_CREATED_ADMIN @@ -0,0 +1 @@ +用户{{ .tenant }}的云主机{{ .name }}已经创建成功。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_DELETED b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_DELETED new file mode 100644 index 0000000000..1f9ea88bc1 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_DELETED @@ -0,0 +1 @@ +您的云主机{{ .name }}已经删除。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_DELETED_ADMIN b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_DELETED_ADMIN new file mode 100644 index 0000000000..c519eb5a89 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_DELETED_ADMIN @@ -0,0 +1 @@ +用户{{ .tenant }}的云主机{{ .name }}已经删除。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_REBUILD_ROOT b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_REBUILD_ROOT new file mode 100644 index 0000000000..98cf1698fe --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SERVER_REBUILD_ROOT @@ -0,0 +1 @@ +您的云主机{{ .name }}的系统盘已经重置成功,{{ if .account }}初始帐号为{{ .account }},{{ end }}{{ if .keypair }}访问密钥为{{ .keypair }},{{ end }}{{ if .password }}初始密码为{{ .password }},{{ end }}请使用{{ if .windows }}远程桌面连接器(RDC){{ else }}SSH{{ end }}或控制面板控制台访问云主机。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SYSTEM_ERROR b/build/region/root/opt/yunion/share/notify_templates/compute/content/SYSTEM_ERROR new file mode 100644 index 0000000000..5da5aa0e01 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SYSTEM_ERROR @@ -0,0 +1 @@ +系统错误消息:{{ .msg }}({{ .created }}) diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/content/SYSTEM_WARNING b/build/region/root/opt/yunion/share/notify_templates/compute/content/SYSTEM_WARNING new file mode 100644 index 0000000000..3e7c63c9cc --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/content/SYSTEM_WARNING @@ -0,0 +1 @@ +系统警告消息:{{ .msg }}({{ .created }}) diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CHANGE_FLAVOR b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CHANGE_FLAVOR new file mode 100644 index 0000000000..0c93926cd7 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CHANGE_FLAVOR @@ -0,0 +1 @@ +您的云主机{{ .name }}的套餐类型已经更改为 CPU:{{ index .flavor_info "cpu" }}核,内存:{{ index .flavor_info "memory" }}M,数据盘:{{ index .flavor_info "datadisk" }}G,带宽: {{ index .flavor_info "ebw" }}M。 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CREATED b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CREATED new file mode 100644 index 0000000000..1d9315af2b --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CREATED @@ -0,0 +1 @@ +云主机{{ .Name }}创建成功 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CREATED_ADMIN b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CREATED_ADMIN new file mode 100644 index 0000000000..cdb60206eb --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_CREATED_ADMIN @@ -0,0 +1 @@ +用户{{ .tenant }}的云主机{{ .name }}已经创建成功 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_DELETED b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_DELETED new file mode 100644 index 0000000000..56f257c962 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_DELETED @@ -0,0 +1 @@ +云主机{{ .name }}删除通知 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_DELETED_ADMIN b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_DELETED_ADMIN new file mode 100644 index 0000000000..090ecb338b --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_DELETED_ADMIN @@ -0,0 +1 @@ +用户{{ .tenant }}的云主机{{ .name }}已经删除 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_REBUILD_ROOT b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_REBUILD_ROOT new file mode 100644 index 0000000000..d2d5d95545 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SERVER_REBUILD_ROOT @@ -0,0 +1 @@ +云主机{{ .name }}的系统盘重置成功 diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SYSTEM_ERROR b/build/region/root/opt/yunion/share/notify_templates/compute/title/SYSTEM_ERROR new file mode 100644 index 0000000000..5da5aa0e01 --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SYSTEM_ERROR @@ -0,0 +1 @@ +系统错误消息:{{ .msg }}({{ .created }}) diff --git a/build/region/root/opt/yunion/share/notify_templates/compute/title/SYSTEM_WARNING b/build/region/root/opt/yunion/share/notify_templates/compute/title/SYSTEM_WARNING new file mode 100644 index 0000000000..3e7c63c9cc --- /dev/null +++ b/build/region/root/opt/yunion/share/notify_templates/compute/title/SYSTEM_WARNING @@ -0,0 +1 @@ +系统警告消息:{{ .msg }}({{ .created }}) diff --git a/cmd/aliyuncli/main.go b/cmd/aliyuncli/main.go index f0252647de..d896e1610b 100644 --- a/cmd/aliyuncli/main.go +++ b/cmd/aliyuncli/main.go @@ -13,6 +13,7 @@ import ( ) type BaseOptions struct { + Debug bool `help:"debug mode"` Help bool `help:"Show help"` AccessKey string `help:"Access key" default:"$ALIYUN_ACCESS_KEY"` Secret string `help:"Secret" default:"$ALIYUN_SECRET"` @@ -69,7 +70,7 @@ func newClient(options *BaseOptions) (*aliyun.SRegion, error) { return nil, fmt.Errorf("Missing secret") } - cli, err := aliyun.NewAliyunClient("", "", options.AccessKey, options.Secret) + cli, err := aliyun.NewAliyunClient("", "", options.AccessKey, options.Secret, options.Debug) if err != nil { return nil, err } diff --git a/cmd/baremetal-agent/main.go b/cmd/baremetal-agent/main.go new file mode 100644 index 0000000000..ef84442e03 --- /dev/null +++ b/cmd/baremetal-agent/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "yunion.io/x/onecloud/pkg/baremetal/service" +) + +func main() { + service.New().StartService() +} diff --git a/cmd/climc/shell/actions.go b/cmd/climc/shell/actions.go index 52c49ad006..331a540225 100644 --- a/cmd/climc/shell/actions.go +++ b/cmd/climc/shell/actions.go @@ -16,6 +16,7 @@ type BaseActionListOptions struct { Descending bool `help:"Descending order"` Action []string `help:"Log action"` Search string `help:"Filter action logs by obj_name, using 'like' syntax."` + Admin bool `help:"admin mode"` } type ActionListOptions struct { @@ -60,6 +61,9 @@ func doActionList(s *mcclient.ClientSession, args *ActionListOptions) error { if len(args.Action) > 0 { params.Add(jsonutils.NewStringArray(args.Action), "action") } + if args.Admin { + params.Add(jsonutils.JSONTrue, "admin") + } logs, err := modules.Actions.List(s, params) if err != nil { return err diff --git a/cmd/climc/shell/alarmtemplates.go b/cmd/climc/shell/alarmtemplates.go index a1e474248a..ae05abed69 100644 --- a/cmd/climc/shell/alarmtemplates.go +++ b/cmd/climc/shell/alarmtemplates.go @@ -346,7 +346,7 @@ func init() { params := jsonutils.NewDict() params.Add(jsonutils.NewInt(args.ENABLED), "enabled") - _, err := modules.AlarmTemplateAlarms.Update(s, args.ALARMTEMPLATE_ID, args.ALARM_ID, params) + _, err := modules.AlarmTemplateAlarms.Update(s, args.ALARMTEMPLATE_ID, args.ALARM_ID, nil, params) if err != nil { return err } diff --git a/cmd/climc/shell/cachedimages.go b/cmd/climc/shell/cachedimages.go index 0e2323086a..3f6f7b3213 100644 --- a/cmd/climc/shell/cachedimages.go +++ b/cmd/climc/shell/cachedimages.go @@ -1,7 +1,6 @@ package shell import ( - "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" @@ -10,16 +9,15 @@ import ( func init() { type CachedImageListOptions struct { options.BaseListOptions + ImageType string `help:"image type" choices:"system|customized|shared|market"` + + Region string `help:"show images cached at cloud region"` + Zone string `help:"show images cached at zone"` } R(&CachedImageListOptions{}, "cached-image-list", "List cached images", func(s *mcclient.ClientSession, args *CachedImageListOptions) error { - var params *jsonutils.JSONDict - { - var err error - params, err = args.BaseListOptions.Params() - if err != nil { - return err - - } + params, err := options.ListStructToParams(args) + if err != nil { + return err } result, err := modules.Cachedimages.List(s, params) if err != nil { @@ -50,12 +48,12 @@ func init() { return nil }) - R(&CachedImageShowOptions{}, "cached-image-delete", "Remove cached image information", func(s *mcclient.ClientSession, args *CachedImageShowOptions) error { - result, err := modules.Cachedimages.Delete(s, args.ID, nil) - if err != nil { - return err - } - printObject(result) + type CachedImageDeleteOptions struct { + ID []string `help:"ID or Name of the cached image to show"` + } + R(&CachedImageDeleteOptions{}, "cached-image-delete", "Remove cached image information", func(s *mcclient.ClientSession, args *CachedImageDeleteOptions) error { + results := modules.Cachedimages.BatchDelete(s, args.ID, nil) + printBatchResults(results, modules.Cachedimages.GetColumns(s)) return nil }) } diff --git a/cmd/climc/shell/cloudaccounts.go b/cmd/climc/shell/cloudaccounts.go index 9c9369875a..78a3cbf568 100644 --- a/cmd/climc/shell/cloudaccounts.go +++ b/cmd/climc/shell/cloudaccounts.go @@ -1,6 +1,8 @@ package shell import ( + "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/mcclient" @@ -31,13 +33,25 @@ func init() { }) type CloudaccountCreateOptions struct { - NAME string `help:"Name of cloud account"` - ACCOUNT string `help:"Account to access the cloud account"` - SECRET string `help:"Secret to access the cloud account, clientId/clientScret for Azure"` - PROVIDER string `help:"Driver for cloud account" choices:"VMware|Aliyun|Azure|Qcloud|OpenStack|Huawei"` - AccessURL string `helo:"hello" metavar:"Azure choices: "` - Desc string `help:"Description"` - Enabled bool `help:"Enabled the account automatically"` + NAME string `help:"Name of cloud account"` + AccessKeyID string `help:"Aiyun|HuaWei|Aws access_key_id"` + AccessKeySecret string `help:"Aiyun|HuaWei|Aws access_key_secret"` + AppID string `help:"Qcloud appid"` + SecretID string `help:"Qcloud secret_id"` + SecretKey string `help:"Qcloud secret_key"` + ProjectName string `help:"OpenStack project_name"` + Username string `help:"OpenStack|VMware username"` + Password string `help:"OpenStack|VMware password"` + AuthURL string `help:"OpenStack auth_url"` + Host string `help:"VMware host"` + Port string `help:"VMware host port" default:"443"` + DirectoryID string `help:"Azure directory_id"` + ClientID string `help:"Azure client_id"` + ClientSecret string `help:"Azure clinet_secret"` + Environment string `help:"Azure|Huawei|Aws environment" choices:"AzureGermanCloud|AzureChinaCloud|AzureUSGovernmentCloud|AzurePublicCloud|InternationalCloud|ChinaCloud|"` + PROVIDER string `help:"Driver for cloud account" choices:"VMware|Aliyun|Azure|Qcloud|OpenStack|Huawei|Aws"` + Desc string `help:"Description"` + Enabled bool `help:"Enabled the account automatically"` Import bool `help:"Import all sub account automatically"` AutoSync bool `help:"Enabled the account automatically"` @@ -46,9 +60,31 @@ func init() { R(&CloudaccountCreateOptions{}, "cloud-account-create", "Create a cloud account", func(s *mcclient.ClientSession, args *CloudaccountCreateOptions) error { params := jsonutils.NewDict() params.Add(jsonutils.NewString(args.NAME), "name") - params.Add(jsonutils.NewString(args.ACCOUNT), "account") - params.Add(jsonutils.NewString(args.SECRET), "secret") params.Add(jsonutils.NewString(args.PROVIDER), "provider") + data := jsonutils.Marshal(args) + + requireParamsMap := map[string][]string{ + "VMware": {"username", "password", "host", "port"}, + "Aliyun": {"access_key_id", "access_key_secret"}, + "Azure": {"directory_id", "client_id", "client_secret", "environment"}, + "Qcloud": {"app_id", "secret_id", "secret_key"}, + "OpenStack": {"project_name", "username", "password", "auth_url"}, + "Huawei": {"access_key_id", "access_key_secret", "environment"}, + "Aws": {"access_key_id", "access_key_secret", "environment"}, + } + + requireParams, ok := requireParamsMap[args.PROVIDER] + if !ok { + return fmt.Errorf("Unsupport provider %s", args.PROVIDER) + } + + for _, key := range requireParams { + v, _ := data.GetString(key) + if len(v) == 0 { + return fmt.Errorf("Missing %s", key) + } + params.Add(jsonutils.NewString(v), key) + } if args.Enabled { params.Add(jsonutils.JSONTrue, "enabled") @@ -64,9 +100,6 @@ func init() { } } - if len(args.AccessURL) > 0 { - params.Add(jsonutils.NewString(args.AccessURL), "access_url") - } if len(args.Desc) > 0 { params.Add(jsonutils.NewString(args.Desc), "description") } @@ -182,15 +215,20 @@ func init() { }) type CloudaccountUpdateCredentialOptions struct { - ID string `help:"ID or Name of cloud account"` - ACCOUNT string `help:"new account"` - SECRET string `help:"new secret"` + ID string `help:"ID or Name of cloud account"` + AccessKeyID string `help:"Aiyun|HuaWei|Aws access_key_id"` + AccessKeySecret string `help:"Aiyun|HuaWei|Aws access_key_secret"` + AppID string `help:"Qcloud appid"` + SecretID string `help:"Qcloud secret_id"` + SecretKey string `help:"Qcloud secret_key"` + ProjectName string `help:"OpenStack project_name"` + Username string `help:"OpenStack|VMware username"` + Password string `help:"OpenStack|VMware password"` + ClientID string `help:"Azure tenant_id"` + ClientSecret string `help:"Azure clinet_secret"` } R(&CloudaccountUpdateCredentialOptions{}, "cloud-account-update-credential", "Update credential of a cloud account", func(s *mcclient.ClientSession, args *CloudaccountUpdateCredentialOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.ACCOUNT), "account") - params.Add(jsonutils.NewString(args.SECRET), "secret") - + params := jsonutils.Marshal(args) result, err := modules.Cloudaccounts.PerformAction(s, args.ID, "update-credential", params) if err != nil { return err diff --git a/cmd/climc/shell/cloudregions.go b/cmd/climc/shell/cloudregions.go index 486a71670a..fb789365bc 100644 --- a/cmd/climc/shell/cloudregions.go +++ b/cmd/climc/shell/cloudregions.go @@ -15,6 +15,8 @@ func init() { Public *bool `help:"show public cloud regions only" json:"is_public"` Usable *bool `help:"List regions where networks are usable"` UsableVpc *bool `help:"List regions where VPC are usable"` + + City string `help:"List regions in the specified city"` } R(&CloudregionListOptions{}, "cloud-region-list", "List cloud regions", func(s *mcclient.ClientSession, opts *CloudregionListOptions) error { params, err := options.ListStructToParams(opts) @@ -29,20 +31,66 @@ func init() { return nil }) + type CloudregionCityListOptions struct { + Manager string `help:"List objects belonging to the cloud provider"` + Account string `help:"List objects belonging to the cloud account"` + Provider string `help:"List objects from the provider" choices:"VMware|Aliyun|Qcloud|Azure|Aws|Huawei|Openstack"` + + Private *bool `help:"show private cloud regions only" json:"is_private"` + Public *bool `help:"show public cloud regions only" json:"is_public"` + Usable *bool `help:"List regions where networks are usable"` + UsableVpc *bool `help:"List regions where VPC are usable"` + } + R(&CloudregionCityListOptions{}, "cloud-region-cities", "List cities where cloud region resides", func(s *mcclient.ClientSession, args *CloudregionCityListOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + results, err := modules.Cloudregions.GetRegionCities(s, params) + if err != nil { + return err + } + listResult := modules.ListResult{} + listResult.Data, err = results.GetArray() + if err != nil { + return err + } + printList(&listResult, nil) + return nil + }) + + R(&CloudregionCityListOptions{}, "cloud-region-providers", "List cities where cloud region resides", func(s *mcclient.ClientSession, args *CloudregionCityListOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + results, err := modules.Cloudregions.GetRegionProviders(s, params) + if err != nil { + return err + } + listResult := modules.ListResult{} + listResult.Data, err = results.GetArray() + if err != nil { + return err + } + printList(&listResult, nil) + return nil + }) + type CloudregionCreateOptions struct { - Id string `help:"ID of the region"` - NAME string `help:"Name of the region"` - Provider string `help:"Cloud provider"` - Desc string `help:"Description"` + Id string `help:"ID of the region"` + NAME string `help:"Name of the region"` + Provider string `help:"Cloud provider"` + Desc string `help:"Description" json:"description" token:"desc"` + Latitude float32 `help:"region geographical location - latitude"` + Longitude float32 `help:"region geographical location - longitude"` + City string `help:"region geograpical location - city, e.g. Beijing, Frankfurt"` + CountryCode string `help:"region geographical location - ISO country code, e.g. CN"` } R(&CloudregionCreateOptions{}, "cloud-region-create", "Create a cloud region", func(s *mcclient.ClientSession, args *CloudregionCreateOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.NAME), "name") - if len(args.Id) > 0 { - params.Add(jsonutils.NewString(args.Id), "id") - } - if len(args.Desc) > 0 { - params.Add(jsonutils.NewString(args.Desc), "description") + params, err := options.StructToParams(args) + if err != nil { + return err } results, err := modules.Cloudregions.Create(s, params) if err != nil { @@ -74,17 +122,18 @@ func init() { }) type CloudregionUpdateOptions struct { - ID string `help:"ID or name of the region"` - Name string `help:"New name of the region"` - Desc string `help:"Description of the region"` + ID string `help:"ID or name of the region to update" json:"-"` + Name string `help:"New name of the region"` + Desc string `help:"Description of the region" json:"description" token:"desc"` + Latitude float32 `help:"region geographical location - latitude"` + Longitude float32 `help:"region geographical location - longitude"` + City string `help:"region geograpical location - city, e.g. Beijing, Frankfurt"` + CountryCode string `help:"region geographical location - ISO country code, e.g. CN"` } R(&CloudregionUpdateOptions{}, "cloud-region-update", "Update a cloud region", func(s *mcclient.ClientSession, args *CloudregionUpdateOptions) error { - params := jsonutils.NewDict() - if len(args.Name) > 0 { - params.Add(jsonutils.NewString(args.Name), "name") - } - if len(args.Desc) > 0 { - params.Add(jsonutils.NewString(args.Desc), "description") + params, err := options.StructToParams(args) + if err != nil { + return err } results, err := modules.Cloudregions.Update(s, args.ID, params) if err != nil { diff --git a/cmd/climc/shell/disks.go b/cmd/climc/shell/disks.go index 43c65e7dc4..9a6af79ebe 100644 --- a/cmd/climc/shell/disks.go +++ b/cmd/climc/shell/disks.go @@ -11,13 +11,13 @@ import ( func init() { type DiskListOptions struct { options.BaseListOptions - - Unused *bool `help:"Show unused disks"` - Share *bool `help:"Show Share storage disks"` - Local *bool `help:"Show Local storage disks"` - Guest string `help:"Guest ID or name"` - Storage string `help:"Storage ID or name"` - Type string `help:"Disk type" choices:"sys|data|swap|volume"` + Unused *bool `help:"Show unused disks"` + Share *bool `help:"Show Share storage disks"` + Local *bool `help:"Show Local storage disks"` + Guest string `help:"Guest ID or name"` + Storage string `help:"Storage ID or name"` + Type string `help:"Disk type" choices:"sys|data|swap|volume"` + CloudType string `help:"Public cloud or private cloud" choices:"Public|Private"` BillingType string `help:"billing type" choices:"postpaid|prepaid"` } @@ -26,6 +26,14 @@ func init() { if err != nil { return err } + if len(opts.CloudType) > 0 { + if opts.CloudType == "Public" { + params.Add(jsonutils.JSONTrue, "public_cloud") + } else if opts.CloudType == "Private" { + params.Add(jsonutils.JSONTrue, "private_cloud") + } + } + result, err := modules.Disks.List(s, params) if err != nil { return err @@ -230,4 +238,41 @@ func init() { printObject(disk) return nil }) + + type DiskSaveOptions struct { + ID string `help:"ID or name of the disk" json:"-"` + NAME string `help:"Image name"` + OSTYPE string `help:"Os type" choices:"Linux|Windows|VMware" json:"-"` + Public *bool `help:"Make the image public available" json:"is_public"` + Format string `help:"image format" choices:"vmdk|qcow2"` + Notes string `help:"Notes about the image"` + } + R(&DiskSaveOptions{}, "disk-save", "Disk save image", func(s *mcclient.ClientSession, args *DiskSaveOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + params.Add(jsonutils.NewString(args.OSTYPE), "properties", "os_type") + disk, err := modules.Disks.PerformAction(s, args.ID, "save", params) + if err != nil { + return err + } + printObject(disk) + return nil + }) + + type DiskUpdateStatusOptions struct { + ID string `help:"ID or name of disk"` + STATUS string `help:"Disk status" choices:"ready"` + } + R(&DiskUpdateStatusOptions{}, "disk-update-status", "Set disk status", func(s *mcclient.ClientSession, args *DiskUpdateStatusOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.STATUS), "status") + disk, err := modules.Disks.PerformAction(s, args.ID, "status", params) + if err != nil { + return err + } + printObject(disk) + return nil + }) } diff --git a/cmd/climc/shell/elasticips.go b/cmd/climc/shell/elasticips.go index 8c0a36269b..ea1f118024 100644 --- a/cmd/climc/shell/elasticips.go +++ b/cmd/climc/shell/elasticips.go @@ -71,6 +71,9 @@ func init() { ID string `help:"ID or name of EIP"` Name string `help:"New name of EIP"` Desc string `help:"New description of EIP"` + + EnableAutoDellocate bool `help:"enable automatically dellocate when dissociate from instance"` + DisableAutoDellocate bool `help:"disable automatically dellocate when dissociate from instance"` } R(&EipUpdateOptions{}, "eip-update", "Update EIP properties", func(s *mcclient.ClientSession, args *EipUpdateOptions) error { params := jsonutils.NewDict() @@ -80,6 +83,11 @@ func init() { if len(args.Desc) > 0 { params.Add(jsonutils.NewString(args.Desc), "description") } + if args.EnableAutoDellocate { + params.Add(jsonutils.JSONTrue, "auto_dellocate") + } else if args.DisableAutoDellocate { + params.Add(jsonutils.JSONFalse, "auto_dellocate") + } result, err := modules.Elasticips.Update(s, args.ID, params) if err != nil { return err @@ -105,11 +113,16 @@ func init() { return nil }) - type EipSingleOptions struct { - ID string `help:"ID or name of EIP"` + type EipDissociateOptions struct { + ID string `help:"ID or name of EIP"` + AutoDelete bool `help:"automatically delete the dissociate EIP" json:"auto_delete,omitfalse"` } - R(&EipSingleOptions{}, "eip-dissociate", "Dissociate an EIP from an instance", func(s *mcclient.ClientSession, args *EipSingleOptions) error { - result, err := modules.Elasticips.PerformAction(s, args.ID, "dissociate", nil) + R(&EipDissociateOptions{}, "eip-dissociate", "Dissociate an EIP from an instance", func(s *mcclient.ClientSession, args *EipDissociateOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + result, err := modules.Elasticips.PerformAction(s, args.ID, "dissociate", params) if err != nil { return err } @@ -117,6 +130,9 @@ func init() { return nil }) + type EipSingleOptions struct { + ID string `help:"ID or name of EIP"` + } R(&EipSingleOptions{}, "eip-sync", "Synchronize status of an EIP", func(s *mcclient.ClientSession, args *EipSingleOptions) error { result, err := modules.Elasticips.PerformAction(s, args.ID, "sync", nil) if err != nil { diff --git a/cmd/climc/shell/hostcachedimages.go b/cmd/climc/shell/hostcachedimages.go index 0eeb17cea6..c150c7fb91 100644 --- a/cmd/climc/shell/hostcachedimages.go +++ b/cmd/climc/shell/hostcachedimages.go @@ -50,7 +50,7 @@ func init() { if params.Size() == 0 { return InvalidUpdateError() } - result, err := modules.Hostcachedimages.Update(s, args.HOST, args.IMAGE, params) + result, err := modules.Hostcachedimages.Update(s, args.HOST, args.IMAGE, nil, params) if err != nil { return err } diff --git a/cmd/climc/shell/hoststorages.go b/cmd/climc/shell/hoststorages.go index a524a911cd..f2b438d308 100644 --- a/cmd/climc/shell/hoststorages.go +++ b/cmd/climc/shell/hoststorages.go @@ -56,7 +56,7 @@ func init() { }) R(&HostStorageDetailOptions{}, "host-storage-detach", "Detach a storage from a host", func(s *mcclient.ClientSession, args *HostStorageDetailOptions) error { - result, err := modules.Hoststorages.Detach(s, args.HOST, args.STORAGE) + result, err := modules.Hoststorages.Detach(s, args.HOST, args.STORAGE, nil) if err != nil { return err } diff --git a/cmd/climc/shell/hostwires.go b/cmd/climc/shell/hostwires.go index acc6e9bf3f..4b606c99bc 100644 --- a/cmd/climc/shell/hostwires.go +++ b/cmd/climc/shell/hostwires.go @@ -78,7 +78,7 @@ func init() { params.Add(jsonutils.JSONFalse, "is_master") } } - result, err := modules.Hostwires.Update(s, args.HOST, args.WIRE, params) + result, err := modules.Hostwires.Update(s, args.HOST, args.WIRE, nil, params) if err != nil { return err } @@ -91,7 +91,7 @@ func init() { WIRE string `help:"ID or Name of Wire"` } R(&HostWireDetachOptions{}, "host-wire-detach", "Detach host from wire", func(s *mcclient.ClientSession, args *HostWireDetachOptions) error { - result, err := modules.Hostwires.Detach(s, args.HOST, args.WIRE) + result, err := modules.Hostwires.Detach(s, args.HOST, args.WIRE, nil) if err != nil { return err } diff --git a/cmd/climc/shell/images.go b/cmd/climc/shell/images.go index 46100e7c1c..1ffed7d655 100644 --- a/cmd/climc/shell/images.go +++ b/cmd/climc/shell/images.go @@ -251,16 +251,17 @@ func init() { ID string `help:"Image ID or name"` } - R(&ImageDetailOptions{}, "image-delete", "Delete a image", func(s *mcclient.ClientSession, args *ImageDetailOptions) error { - imgID, err := modules.Images.GetId(s, args.ID, nil) + type ImageDeleteOptions struct { + ID []string `help:"Image ID or name"` + OverridePendingDelete *bool `help:"Delete image directly instead of pending delete"` + } + R(&ImageDeleteOptions{}, "image-delete", "Delete a image", func(s *mcclient.ClientSession, args *ImageDeleteOptions) error { + params, err := options.StructToParams(args) if err != nil { return err } - if result, err := modules.Images.Delete(s, imgID, nil); err != nil { - return err - } else { - printObject(result) - } + ret := modules.Images.BatchDeleteWithParam(s, args.ID, params, nil) + printBatchResults(ret, modules.Images.GetColumns(s)) return nil }) diff --git a/cmd/climc/shell/loadbalanceracls.go b/cmd/climc/shell/loadbalanceracls.go index 70f3baf9ed..c0e36d0704 100644 --- a/cmd/climc/shell/loadbalanceracls.go +++ b/cmd/climc/shell/loadbalanceracls.go @@ -93,6 +93,14 @@ func init() { printLbAcl(lbacl) return nil }) + R(&options.LoadbalancerAclDeleteOptions{}, "lbacl-purge", "Purge lbacl", func(s *mcclient.ClientSession, opts *options.LoadbalancerAclDeleteOptions) error { + lbacl, err := modules.LoadbalancerAcls.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printLbAcl(lbacl) + return nil + }) R(&options.LoadbalancerAclDeleteOptions{}, "lbacl-delete", "Show lbacl", func(s *mcclient.ClientSession, opts *options.LoadbalancerAclDeleteOptions) error { lbacl, err := modules.LoadbalancerAcls.Delete(s, opts.ID, nil) if err != nil { diff --git a/cmd/climc/shell/loadbalancerbackendgroups.go b/cmd/climc/shell/loadbalancerbackendgroups.go index 068ac36bc4..7212c9997b 100644 --- a/cmd/climc/shell/loadbalancerbackendgroups.go +++ b/cmd/climc/shell/loadbalancerbackendgroups.go @@ -8,7 +8,7 @@ import ( func init() { R(&options.LoadbalancerBackendGroupCreateOptions{}, "lbbackendgroup-create", "Create lbbackendgroup", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendGroupCreateOptions) error { - params, err := options.StructToParams(opts) + params, err := opts.Params() if err != nil { return err } @@ -51,7 +51,7 @@ func init() { printObject(lbbackendgroup) return nil }) - R(&options.LoadbalancerBackendGroupDeleteOptions{}, "lbbackendgroup-delete", "Show lbbackendgroup", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendGroupDeleteOptions) error { + R(&options.LoadbalancerBackendGroupDeleteOptions{}, "lbbackendgroup-delete", "Delete lbbackendgroup", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendGroupDeleteOptions) error { lbbackendgroup, err := modules.LoadbalancerBackendGroups.Delete(s, opts.ID, nil) if err != nil { return err @@ -59,4 +59,12 @@ func init() { printObject(lbbackendgroup) return nil }) + R(&options.LoadbalancerBackendGroupDeleteOptions{}, "lbbackendgroup-purge", "Purge lbbackendgroup", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendGroupDeleteOptions) error { + lbbackendgroup, err := modules.LoadbalancerBackendGroups.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printObject(lbbackendgroup) + return nil + }) } diff --git a/cmd/climc/shell/loadbalancerbackends.go b/cmd/climc/shell/loadbalancerbackends.go index f8e1c4d03e..e7c991b6a0 100644 --- a/cmd/climc/shell/loadbalancerbackends.go +++ b/cmd/climc/shell/loadbalancerbackends.go @@ -48,7 +48,7 @@ func init() { printObject(lbbackend) return nil }) - R(&options.LoadbalancerBackendDeleteOptions{}, "lbbackend-delete", "Show lbbackend", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendDeleteOptions) error { + R(&options.LoadbalancerBackendDeleteOptions{}, "lbbackend-delete", "Delete lbbackend", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendDeleteOptions) error { lbbackend, err := modules.LoadbalancerBackends.Delete(s, opts.ID, nil) if err != nil { return err @@ -56,4 +56,12 @@ func init() { printObject(lbbackend) return nil }) + R(&options.LoadbalancerBackendDeleteOptions{}, "lbbackend-purge", "Purge lbbackend", func(s *mcclient.ClientSession, opts *options.LoadbalancerBackendDeleteOptions) error { + lbbackend, err := modules.LoadbalancerBackends.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printObject(lbbackend) + return nil + }) } diff --git a/cmd/climc/shell/loadbalancercertificates.go b/cmd/climc/shell/loadbalancercertificates.go index 21882b5165..3aed443386 100644 --- a/cmd/climc/shell/loadbalancercertificates.go +++ b/cmd/climc/shell/loadbalancercertificates.go @@ -51,7 +51,7 @@ func init() { printObject(lbcert) return nil }) - R(&options.LoadbalancerCertificateDeleteOptions{}, "lbcert-delete", "Show lbcert", func(s *mcclient.ClientSession, opts *options.LoadbalancerCertificateDeleteOptions) error { + R(&options.LoadbalancerCertificateDeleteOptions{}, "lbcert-delete", "Delete lbcert", func(s *mcclient.ClientSession, opts *options.LoadbalancerCertificateDeleteOptions) error { lbcert, err := modules.LoadbalancerCertificates.Delete(s, opts.ID, nil) if err != nil { return err @@ -59,4 +59,12 @@ func init() { printObject(lbcert) return nil }) + R(&options.LoadbalancerCertificateDeleteOptions{}, "lbcert-purge", "Purge lbcert", func(s *mcclient.ClientSession, opts *options.LoadbalancerCertificateDeleteOptions) error { + lbcert, err := modules.LoadbalancerCertificates.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printObject(lbcert) + return nil + }) } diff --git a/cmd/climc/shell/loadbalancerlistenerrules.go b/cmd/climc/shell/loadbalancerlistenerrules.go index 403dbd9acc..60d4660bc5 100644 --- a/cmd/climc/shell/loadbalancerlistenerrules.go +++ b/cmd/climc/shell/loadbalancerlistenerrules.go @@ -46,7 +46,7 @@ func init() { printObject(lblistenerrule) return nil }) - R(&options.LoadbalancerListenerRuleDeleteOptions{}, "lblistenerrule-delete", "Show lblistenerrule", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerRuleDeleteOptions) error { + R(&options.LoadbalancerListenerRuleDeleteOptions{}, "lblistenerrule-delete", "Delete lblistenerrule", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerRuleDeleteOptions) error { lblistenerrule, err := modules.LoadbalancerListenerRules.Delete(s, opts.ID, nil) if err != nil { return err @@ -54,6 +54,14 @@ func init() { printObject(lblistenerrule) return nil }) + R(&options.LoadbalancerListenerRuleDeleteOptions{}, "lblistenerrule-purge", "Purge lblistenerrule", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerRuleDeleteOptions) error { + lblistenerrule, err := modules.LoadbalancerListenerRules.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printObject(lblistenerrule) + return nil + }) R(&options.LoadbalancerListenerRuleActionStatusOptions{}, "lblistenerrule-status", "Change lblistenerrule status", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerRuleActionStatusOptions) error { params, err := options.StructToParams(opts) if err != nil { diff --git a/cmd/climc/shell/loadbalancerlisteners.go b/cmd/climc/shell/loadbalancerlisteners.go index 2c60b61c05..d88b28b4fc 100644 --- a/cmd/climc/shell/loadbalancerlisteners.go +++ b/cmd/climc/shell/loadbalancerlisteners.go @@ -48,7 +48,7 @@ func init() { printObject(lblistener) return nil }) - R(&options.LoadbalancerListenerDeleteOptions{}, "lblistener-delete", "Show lblistener", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerDeleteOptions) error { + R(&options.LoadbalancerListenerDeleteOptions{}, "lblistener-delete", "Delete lblistener", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerDeleteOptions) error { lblistener, err := modules.LoadbalancerListeners.Delete(s, opts.ID, nil) if err != nil { return err @@ -56,6 +56,14 @@ func init() { printObject(lblistener) return nil }) + R(&options.LoadbalancerListenerDeleteOptions{}, "lblistener-purge", "Purge lblistener", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerDeleteOptions) error { + lblistener, err := modules.LoadbalancerListeners.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printObject(lblistener) + return nil + }) R(&options.LoadbalancerListenerActionStatusOptions{}, "lblistener-status", "Change lblistener status", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerActionStatusOptions) error { params, err := options.StructToParams(opts) if err != nil { @@ -68,4 +76,12 @@ func init() { printObject(lblistener) return nil }) + R(&options.LoadbalancerListenerActionSyncStatusOptions{}, "lblistener-syncstatus", "Sync lblistener status", func(s *mcclient.ClientSession, opts *options.LoadbalancerListenerActionSyncStatusOptions) error { + lblistener, err := modules.LoadbalancerListeners.PerformAction(s, opts.ID, "syncstatus", nil) + if err != nil { + return err + } + printObject(lblistener) + return nil + }) } diff --git a/cmd/climc/shell/loadbalancernetworks.go b/cmd/climc/shell/loadbalancernetworks.go new file mode 100644 index 0000000000..c11d3cf821 --- /dev/null +++ b/cmd/climc/shell/loadbalancernetworks.go @@ -0,0 +1,46 @@ +package shell + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type LoadbalancerNetworkListOptions struct { + options.BaseListOptions + Loadbalancer string `help:"ID or Name of Loadbalancer"` + Network string `help:"ID or Name of network"` + Ip string `help:"search the IP address"` + } + R(&LoadbalancerNetworkListOptions{}, "loadbalancer-network-list", "List loadbalancer network pairs", func(s *mcclient.ClientSession, args *LoadbalancerNetworkListOptions) error { + var params *jsonutils.JSONDict + { + var err error + params, err = args.BaseListOptions.Params() + if err != nil { + return err + + } + } + if len(args.Ip) > 0 { + params.Add(jsonutils.NewString(args.Ip), "ip_addr") + } + var result *modules.ListResult + var err error + if len(args.Loadbalancer) > 0 { + result, err = modules.Loadbalancernetworks.ListDescendent(s, args.Loadbalancer, params) + } else if len(args.Network) > 0 { + result, err = modules.Loadbalancernetworks.ListDescendent2(s, args.Network, params) + } else { + result, err = modules.Loadbalancernetworks.List(s, params) + } + if err != nil { + return err + } + printList(result, modules.Loadbalancernetworks.GetColumns(s)) + return nil + }) + +} diff --git a/cmd/climc/shell/loadbalancers.go b/cmd/climc/shell/loadbalancers.go index 5ee9d53f1d..1024f9f6ce 100644 --- a/cmd/climc/shell/loadbalancers.go +++ b/cmd/climc/shell/loadbalancers.go @@ -48,7 +48,7 @@ func init() { printObject(lb) return nil }) - R(&options.LoadbalancerDeleteOptions{}, "lb-delete", "Show lb", func(s *mcclient.ClientSession, opts *options.LoadbalancerDeleteOptions) error { + R(&options.LoadbalancerDeleteOptions{}, "lb-delete", "Delete lb", func(s *mcclient.ClientSession, opts *options.LoadbalancerDeleteOptions) error { lb, err := modules.Loadbalancers.Delete(s, opts.ID, nil) if err != nil { return err @@ -56,6 +56,14 @@ func init() { printObject(lb) return nil }) + R(&options.LoadbalancerPurgeOptions{}, "lb-purge", "Purge lb", func(s *mcclient.ClientSession, opts *options.LoadbalancerPurgeOptions) error { + lb, err := modules.Loadbalancers.PerformAction(s, opts.ID, "purge", nil) + if err != nil { + return err + } + printObject(lb) + return nil + }) R(&options.LoadbalancerActionStatusOptions{}, "lb-status", "Change lb status", func(s *mcclient.ClientSession, opts *options.LoadbalancerActionStatusOptions) error { params, err := options.StructToParams(opts) if err != nil { @@ -68,4 +76,12 @@ func init() { printObject(lb) return nil }) + R(&options.LoadbalancerActionSyncStatusOptions{}, "lb-syncstatus", "Sync lb status", func(s *mcclient.ClientSession, opts *options.LoadbalancerActionSyncStatusOptions) error { + lb, err := modules.Loadbalancers.PerformAction(s, opts.ID, "syncstatus", nil) + if err != nil { + return err + } + printObject(lb) + return nil + }) } diff --git a/cmd/climc/shell/meteralert.go b/cmd/climc/shell/meteralert.go new file mode 100644 index 0000000000..f7f6e5e2e2 --- /dev/null +++ b/cmd/climc/shell/meteralert.go @@ -0,0 +1,103 @@ +package shell + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + + /** + * 创建一条报警规则 + */ + type MeterAlertCreateOptions struct { + TYPE string `help:"Alert rule type" choices:"balance|resFee|monthFee"` + PROVIDER string `help:"Name of the cloud platform"` + ACCOUNT_ID string `help:"ID of the cloud platform"` + THRESHOLD float64 `help:"Threshold value of the metric"` + COMPARATOR string `help:"Comparison operator for join expressions" choices:">|<|>=|<=|=|!="` + RECIPIENTS string `help:"Comma separated recipient ID"` + LEVEL string `help:"Alert level" choices:"normal|important|fatal"` + CHANNEL string `help:"Ways to send an alarm" choices:"email|mobile"` + } + R(&MeterAlertCreateOptions{}, "meteralert-create", "Create a meter alert rule", func(s *mcclient.ClientSession, args *MeterAlertCreateOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.TYPE), "type") + params.Add(jsonutils.NewString(args.PROVIDER), "provider") + params.Add(jsonutils.NewString(args.ACCOUNT_ID), "account_id") + params.Add(jsonutils.NewFloat(args.THRESHOLD), "threshold") + params.Add(jsonutils.NewString(args.COMPARATOR), "comparator") + params.Add(jsonutils.NewString(args.RECIPIENTS), "recipients") + params.Add(jsonutils.NewString(args.LEVEL), "level") + params.Add(jsonutils.NewString(args.CHANNEL), "channel") + + rst, err := modules.MeterAlert.Create(s, params) + + if err != nil { + return err + } + + printObject(rst) + return nil + }) + + /** + * 删除指定ID的报警规则 + */ + type MeterAlertDeleteOptions struct { + ID string `help:"ID of alarm"` + } + R(&MeterAlertDeleteOptions{}, "meteralert-delete", "Delete a meter alert", func(s *mcclient.ClientSession, args *MeterAlertDeleteOptions) error { + alarm, e := modules.MeterAlert.Delete(s, args.ID, nil) + if e != nil { + return e + } + printObject(alarm) + return nil + }) + + /** + * 修改指定ID的报警规则状态 + */ + type MeterAlertUpdateOptions struct { + ID string `help:"ID of the meter alert"` + STATUS string `help:"Name of the new alarm" choices:"Enabled|Disabled"` + } + R(&MeterAlertUpdateOptions{}, "meteralert-change-status", "Change status of a meter alert", func(s *mcclient.ClientSession, args *MeterAlertUpdateOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.STATUS), "status") + + alarm, err := modules.MeterAlert.Patch(s, args.ID, params) + if err != nil { + return err + } + printObject(alarm) + return nil + }) + + /** + * 列出报警规则 + */ + type MeterAlertListOptions struct { + options.BaseListOptions + } + R(&MeterAlertListOptions{}, "meteralert-list", "List meter alert", func(s *mcclient.ClientSession, args *MeterAlertListOptions) error { + var params *jsonutils.JSONDict + { + var err error + params, err = args.BaseListOptions.Params() + if err != nil { + return err + + } + } + result, err := modules.MeterAlert.List(s, params) + if err != nil { + return err + } + printList(result, modules.MeterAlert.GetColumns(s)) + return nil + }) +} diff --git a/cmd/climc/shell/monitortemplates.go b/cmd/climc/shell/monitortemplates.go index 4ad2ad2bfa..1256e2abdf 100644 --- a/cmd/climc/shell/monitortemplates.go +++ b/cmd/climc/shell/monitortemplates.go @@ -163,7 +163,7 @@ func init() { params.Add(jsonutils.NewString(args.MonitorConfig), "monitor_conf_value") } - monitor_template_inputs, err := modules.MonitorTemplateInputs.Update(s, args.MONITOR_TEMPLATE_ID, args.MONITOR_INPUT_NAME, params) + monitor_template_inputs, err := modules.MonitorTemplateInputs.Update(s, args.MONITOR_TEMPLATE_ID, args.MONITOR_INPUT_NAME, nil, params) if err != nil { return err diff --git a/cmd/climc/shell/networks.go b/cmd/climc/shell/networks.go index 0ea837eabf..a27505e06a 100644 --- a/cmd/climc/shell/networks.go +++ b/cmd/climc/shell/networks.go @@ -17,7 +17,9 @@ func init() { Wire string `help:"search networks belongs to a wire" json:"-"` Vpc string `help:"search networks belongs to a VPC"` Region string `help:"search networks belongs to a CloudRegion" json:"cloudregion"` - ServerType string `help:"search networks belongs to a ServerType"` + ServerType string `help:"search networks belongs to a ServerType" choices:"guest|baremetal|container|pxe|ipmi"` + Private *bool `help:"show private cloud networks only" json:"is_private"` + Public *bool `help:"show public cloud networks only" json:"is_public"` } R(&NetworkListOptions{}, "network-list", "List networks", func(s *mcclient.ClientSession, opts *NetworkListOptions) error { params, err := options.ListStructToParams(opts) @@ -41,7 +43,7 @@ func init() { ID string `help:"ID or Name of zone to update"` Name string `help:"Name of zone"` Desc string `metavar:"" help:"Description"` - ServerType string `help:"server type," choices:"baremetal|guest|container"` + ServerType string `help:"server type," choices:"baremetal|guest|container|pxe|ipmi"` StartIp string `help:"Start ip"` EndIp string `help:"end ip"` NetMask int64 `help:"Netmask"` @@ -183,7 +185,7 @@ func init() { Gateway string `help:"Default gateway"` VlanId int64 `help:"Vlan ID" default:"1"` AllocPolicy string `help:"Address allocation policy" choices:"none|stepdown|stepup|random"` - ServerType string `help:"Server type" choices:"baremetal|guest|container"` + ServerType string `help:"Server type" choices:"baremetal|guest|container|pxe|ipmi"` Desc string `help:"Description" metavar:"DESCRIPTION"` } R(&NetworkCreateOptions{}, "network-create", "Create a virtual network", func(s *mcclient.ClientSession, args *NetworkCreateOptions) error { diff --git a/cmd/climc/shell/notification.go b/cmd/climc/shell/notification.go index d6a0641854..03c3f7a4fb 100644 --- a/cmd/climc/shell/notification.go +++ b/cmd/climc/shell/notification.go @@ -1,10 +1,10 @@ package shell import ( - //"strings" "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/mcclient/options" ) @@ -13,35 +13,38 @@ func init() { /** * 新建一个通知发送任务 */ + type NotificationCreateOptions struct { UID string `help:"The user you wanna sent to (Keystone User ID)"` - CONTACTTYPE string `help:"User's contacts type, maybe email|mobile|dingtalk" choices:"email|mobile|dingtalk"` + CONTACTTYPE string `help:"User's contacts type, cloud be email|mobile|dingtalk|webconsole" choices:"email|mobile|dingtalk|webconsole"` TOPIC string `help:"Title or topic of the notification"` PRIORITY string `help:"Priority of the notification maybe normal|important|fatal" choices:"normal|important|fatal"` MSG string `help:"The content of the notification"` Remark string `help:"Remark or description of the notification"` Group bool `help:"Send to group"` + Channel string `help:"User's contacts type, cloud be email|mobile|dingtalk|webconsole" choices:"email|mobile|dingtalk|webconsole"` } R(&NotificationCreateOptions{}, "notify", "Send a notification to sb", func(s *mcclient.ClientSession, args *NotificationCreateOptions) error { - params := jsonutils.NewDict() + msg := notify.SNotifyMessage{} if args.Group { - params.Add(jsonutils.NewString(args.UID), "gid") + msg.Gid = args.UID } else { - params.Add(jsonutils.NewString(args.UID), "uid") - } - params.Add(jsonutils.NewString(args.CONTACTTYPE), "contact_type") - params.Add(jsonutils.NewString(args.TOPIC), "topic") - params.Add(jsonutils.NewString(args.PRIORITY), "priority") - params.Add(jsonutils.NewString(args.MSG), "msg") - if len(args.Remark) > 0 { - params.Add(jsonutils.NewString(args.Remark), "remark") + msg.Uid = args.UID } - notification, err := modules.Notifications.Create(s, params) + msg.ContactType = []notify.TNotifyChannel{notify.TNotifyChannel(args.CONTACTTYPE)} + for _, c := range args.Channel { + msg.ContactType = append(msg.ContactType, notify.TNotifyChannel(c)) + } + msg.Topic = args.TOPIC + msg.Priority = notify.TNotifyPriority(args.PRIORITY) + msg.Msg = args.MSG + msg.Remark = args.Remark + + err := notify.Notifications.Send(s, msg) if err != nil { return err } - printObject(notification) return nil }) @@ -60,7 +63,7 @@ func init() { params.Add(jsonutils.NewString(args.Remark), "remark") } - notification, err := modules.Notifications.Put(s, args.ID, params) + notification, err := notify.Notifications.Put(s, args.ID, params) if err != nil { return err } @@ -75,12 +78,12 @@ func init() { options.BaseListOptions } R(&NotificationListOptions{}, "notify-list", "List notification history", func(s *mcclient.ClientSession, args *NotificationListOptions) error { - result, err := modules.Notifications.List(s, nil) + result, err := notify.Notifications.List(s, nil) if err != nil { return err } - printList(result, modules.Notifications.GetColumns(s)) + printList(result, notify.Notifications.GetColumns(s)) return nil }) diff --git a/cmd/climc/shell/reservedips.go b/cmd/climc/shell/reservedips.go index 6ce216ff58..e0603cca34 100644 --- a/cmd/climc/shell/reservedips.go +++ b/cmd/climc/shell/reservedips.go @@ -9,13 +9,13 @@ import ( func init() { type NetworkReserveIPOptions struct { - NETWORK string `help:"IP or name of network"` - IP string `help:"IP to reserve"` - NOTES string `help:"Why reserve this IP"` + NETWORK string `help:"IP or name of network"` + NOTES string `help:"Why reserve this IP"` + IPS []string `help:"IPs to reserve"` } R(&NetworkReserveIPOptions{}, "network-reserve-ip", "Reserve an IP address from pool", func(s *mcclient.ClientSession, args *NetworkReserveIPOptions) error { params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.IP), "ip") + params.Add(jsonutils.NewStringArray(args.IPS), "ips") params.Add(jsonutils.NewString(args.NOTES), "notes") net, err := modules.Networks.PerformAction(s, args.NETWORK, "reserve-ip", params) if err != nil { diff --git a/cmd/climc/shell/schedtaghosts.go b/cmd/climc/shell/schedtaghosts.go index cb719cfb51..4a3eee5f19 100644 --- a/cmd/climc/shell/schedtaghosts.go +++ b/cmd/climc/shell/schedtaghosts.go @@ -53,7 +53,7 @@ func init() { }) R(&SchedtagHostPair{}, "schedtag-host-remove", "Remove a schedtag from a host", func(s *mcclient.ClientSession, args *SchedtagHostPair) error { - schedtag, err := modules.Schedtaghosts.Detach(s, args.SCHEDTAG, args.HOST) + schedtag, err := modules.Schedtaghosts.Detach(s, args.SCHEDTAG, args.HOST, nil) if err != nil { return err } diff --git a/cmd/climc/shell/serverdisks.go b/cmd/climc/shell/serverdisks.go index e9a5a12f2b..f0e350ae15 100644 --- a/cmd/climc/shell/serverdisks.go +++ b/cmd/climc/shell/serverdisks.go @@ -82,7 +82,7 @@ func init() { if params.Size() == 0 { return InvalidUpdateError() } - srv, err := modules.Serverdisks.Update(s, args.SERVER, args.DISK, params) + srv, err := modules.Serverdisks.Update(s, args.SERVER, args.DISK, nil, params) if err != nil { return err } diff --git a/cmd/climc/shell/servernetworks.go b/cmd/climc/shell/servernetworks.go index c9bb5a0123..5a95d19da7 100644 --- a/cmd/climc/shell/servernetworks.go +++ b/cmd/climc/shell/servernetworks.go @@ -1,7 +1,12 @@ package shell import ( + "fmt" + "strconv" + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" @@ -31,9 +36,6 @@ func init() { if len(args.Ip) > 0 { params.Add(jsonutils.NewString(args.Ip), "ip_addr") } - if len(args.Network) > 0 { - params.Add(jsonutils.NewString(args.Network), "network_id") - } var result *modules.ListResult var err error if len(args.Server) > 0 { @@ -53,9 +55,14 @@ func init() { type ServerNetworkDetailOptions struct { SERVER string `help:"ID or Name of Server"` NETWORK string `help:"ID or Name of Network"` + Mac string `help:"Mac of the guest NIC"` } R(&ServerNetworkDetailOptions{}, "server-network-show", "Show server network details", func(s *mcclient.ClientSession, args *ServerNetworkDetailOptions) error { - result, err := modules.Servernetworks.Get(s, args.SERVER, args.NETWORK, nil) + query := jsonutils.NewDict() + if len(args.Mac) > 0 { + query.Add(jsonutils.NewString(args.Mac), "mac") + } + result, err := modules.Servernetworks.Get(s, args.SERVER, args.NETWORK, query) if err != nil { return err } @@ -66,6 +73,7 @@ func init() { type ServerNetworkUpdateOptions struct { SERVER string `help:"ID or Name of Server"` NETWORK string `help:"ID or Name of Wire"` + Mac string `help:"Mac of NIC"` Driver string `help:"Driver model of vNIC" choices:"virtio|e1000|vmxnet3|rtl8139"` Index int64 `help:"Index of NIC" default:"-1"` Ifname string `help:"Interface name of vNIC on host"` @@ -84,7 +92,11 @@ func init() { if params.Size() == 0 { return InvalidUpdateError() } - result, err := modules.Servernetworks.Update(s, args.SERVER, args.NETWORK, params) + query := jsonutils.NewDict() + if len(args.Mac) > 0 { + query.Add(jsonutils.NewString(args.Mac), "mac") + } + result, err := modules.Servernetworks.Update(s, args.SERVER, args.NETWORK, query, params) if err != nil { return err } @@ -93,13 +105,25 @@ func init() { }) type ServerNetworkBWOptions struct { - SERVER string `help:"ID or Name of server"` - INDEX int64 `help:"Index of NIC"` - BW int64 `help:"Bandwidth in Mbps"` + SERVER string `help:"ID or Name of server"` + MACORIP string `help:"IP, Mac, or Index of NIC"` + BW int64 `help:"Bandwidth in Mbps"` } R(&ServerNetworkBWOptions{}, "server-change-bandwidth", "Change server network bandwidth in Mbps", func(s *mcclient.ClientSession, args *ServerNetworkBWOptions) error { params := jsonutils.NewDict() - params.Add(jsonutils.NewInt(args.INDEX), "index") + if regutils.MatchMacAddr(args.MACORIP) { + params.Add(jsonutils.NewString(args.MACORIP), "mac") + } else if regutils.MatchIP4Addr(args.MACORIP) { + params.Add(jsonutils.NewString(args.MACORIP), "ip_addr") + } else if regutils.MatchInteger(args.MACORIP) { + index, err := strconv.ParseInt(args.MACORIP, 10, 64) + if err != nil { + return err + } + params.Add(jsonutils.NewInt(index), "index") + } else { + return fmt.Errorf("Please specify Ip or Mac") + } params.Add(jsonutils.NewInt(args.BW), "bandwidth") server, err := modules.Servers.PerformAction(s, args.SERVER, "change-bandwidth", params) if err != nil { @@ -126,15 +150,30 @@ func init() { type ServerDetachNetworkOptions struct { SERVER string `help:"ID or Name of server"` - NETWORK string `help:"ID or Name of network to detach"` + MACORIP string `help:"Mac Or IP of NIC"` Reserve bool `help:"Put the release IP address into reserved address pool"` } R(&ServerDetachNetworkOptions{}, "server-detach-network", "Detach the virtual network fron a virtual server", func(s *mcclient.ClientSession, args *ServerDetachNetworkOptions) error { params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.NETWORK), "net_id") + // params.Add(jsonutils.NewString(args.NETWORK), "net_id") if args.Reserve { params.Add(jsonutils.JSONTrue, "reserve") } + if regutils.MatchMacAddr(args.MACORIP) { + params.Add(jsonutils.NewString(args.MACORIP), "mac") + } else if regutils.MatchIP4Addr(args.MACORIP) { + params.Add(jsonutils.NewString(args.MACORIP), "ip_addr") + } else if regutils.MatchInteger(args.MACORIP) { + index, err := strconv.ParseInt(args.MACORIP, 10, 64) + if err != nil { + return err + } + params.Add(jsonutils.NewInt(index), "index") + } else if len(args.MACORIP) > 0 { + params.Add(jsonutils.NewString(args.MACORIP), "net_id") + } else { + return fmt.Errorf("Please specify Ip or Mac") + } srv, err := modules.Servers.PerformAction(s, args.SERVER, "detachnetwork", params) if err != nil { return err @@ -143,4 +182,37 @@ func init() { return nil }) + type ServerChangeIPAddressOptions struct { + SERVER string `help:"ID or Name of server"` + MACORIP string `help:"Mac Or IP of NIC"` + NETDESC string `help:"Network description"` + Reserve bool `help:"Put the release IP address into reserved address pool"` + } + R(&ServerChangeIPAddressOptions{}, "server-change-ipaddr", "Change ipaddr of a virtual server", func(s *mcclient.ClientSession, args *ServerChangeIPAddressOptions) error { + params := jsonutils.NewDict() + if args.Reserve { + params.Add(jsonutils.JSONTrue, "reserve") + } + if regutils.MatchMacAddr(args.MACORIP) { + params.Add(jsonutils.NewString(args.MACORIP), "mac") + } else if regutils.MatchIP4Addr(args.MACORIP) { + params.Add(jsonutils.NewString(args.MACORIP), "ip_addr") + } else if regutils.MatchInteger(args.MACORIP) { + index, err := strconv.ParseInt(args.MACORIP, 10, 64) + if err != nil { + return err + } + params.Add(jsonutils.NewInt(index), "index") + } else { + return fmt.Errorf("Please specify Ip or Mac") + } + params.Add(jsonutils.NewString(args.NETDESC), "net_desc") + srv, err := modules.Servers.PerformAction(s, args.SERVER, "change-ipaddr", params) + if err != nil { + return err + } + printObject(srv) + return nil + }) + } diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index 961525c62e..27cbae80de 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -166,6 +166,15 @@ func init() { return nil }) + R(&options.ServerDeleteBackupOptions{}, "server-delete-backup", "Guest delete backup", func(s *mcclient.ClientSession, opts *options.ServerDeleteBackupOptions) error { + ret, err := modules.Servers.PerformAction(s, opts.ID, "delete-backup", nil) + if err != nil { + return err + } + printObject(ret) + return nil + }) + R(&options.ServerStopOptions{}, "server-stop", "Stop servers", func(s *mcclient.ClientSession, opts *options.ServerStopOptions) error { params, err := options.StructToParams(opts) if err != nil { @@ -560,10 +569,15 @@ func init() { }) type ServerDissociateEipOptions struct { - ID string `help:"ID or name of server"` + ID string `help:"ID or name of server" json:"-"` + AutoDelete bool `help:"automatically delete the dissociate EIP" json:"auto_delete,omitfalse"` } R(&ServerDissociateEipOptions{}, "server-dissociate-eip", "Dissociate an eip from a server", func(s *mcclient.ClientSession, args *ServerDissociateEipOptions) error { - result, err := modules.Servers.PerformAction(s, args.ID, "dissociate-eip", nil) + params, err := options.StructToParams(args) + if err != nil { + return err + } + result, err := modules.Servers.PerformAction(s, args.ID, "dissociate-eip", params) if err != nil { return nil } diff --git a/cmd/climc/shell/storagecachedimages.go b/cmd/climc/shell/storagecachedimages.go index 0b2579d5b2..0b0095a088 100644 --- a/cmd/climc/shell/storagecachedimages.go +++ b/cmd/climc/shell/storagecachedimages.go @@ -52,7 +52,7 @@ func init() { if params.Size() == 0 { return InvalidUpdateError() } - result, err := modules.Storagecachedimages.Update(s, args.STORAGECACHE, args.IMAGE, params) + result, err := modules.Storagecachedimages.Update(s, args.STORAGECACHE, args.IMAGE, nil, params) if err != nil { return err } diff --git a/cmd/climc/shell/storagecaches.go b/cmd/climc/shell/storagecaches.go index baf58fc8d5..5fe2384b60 100644 --- a/cmd/climc/shell/storagecaches.go +++ b/cmd/climc/shell/storagecaches.go @@ -10,6 +10,8 @@ import ( func init() { type StoragecacheListOptions struct { options.BaseListOptions + + CloudregionId string `help:"cloudregion id"` } R(&StoragecacheListOptions{}, "storage-cache-list", "List storage caches", func(s *mcclient.ClientSession, opts *StoragecacheListOptions) error { params, err := options.ListStructToParams(opts) @@ -71,7 +73,7 @@ func init() { type StorageUncacheImageActionOptions struct { ID string `help:"ID or name of storage"` IMAGE string `help:"ID or name of image"` - Force bool `help:"Force uncache, even if the image exists in cache"` + Force bool `help:"Force uncache, even if the image exists in cache is invalid"` } R(&StorageUncacheImageActionOptions{}, "storagecache-uncache-image", "Ask a storage cache to remove image from its cache", func(s *mcclient.ClientSession, args *StorageUncacheImageActionOptions) error { params := jsonutils.NewDict() diff --git a/cmd/host/main.go b/cmd/host/main.go new file mode 100644 index 0000000000..f8c0dec1e0 --- /dev/null +++ b/cmd/host/main.go @@ -0,0 +1,8 @@ +package main + +import "yunion.io/x/onecloud/pkg/hostman" + +func main() { + var srv = hostman.SHostService{} + srv.StartService() +} diff --git a/cmd/huaweicli/main.go b/cmd/huaweicli/main.go new file mode 100644 index 0000000000..b23535f7bc --- /dev/null +++ b/cmd/huaweicli/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "fmt" + "os" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/util/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" + "yunion.io/x/structarg" + + _ "yunion.io/x/onecloud/pkg/util/huawei/shell" +) + +type BaseOptions struct { + Help bool `help:"Show help" default:"false"` + Debug bool `help:"Show debug" default:"false"` + CloudEnv string `help:"Cloud environment" default:"$HUAWEI_CLOUD_ENV" choices:"ChinaCloud|InternationalCloud"` + AccessKey string `help:"Access key" default:"$HUAWEI_ACCESS_KEY"` + Secret string `help:"Secret" default:"$HUAWEI_SECRET"` + RegionId string `help:"RegionId" default:"$HUAWEI_REGION"` + ProjectId string `help:"RegionId" default:"$HUAWEI_PROJECT"` + SUBCOMMAND string `help:"huaweicli subcommand" subcommand:"true"` +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parse, e := structarg.NewArgumentParser(&BaseOptions{}, + "huaweicli", + "Command-line interface to huawei API.", + `See "huaweicli help COMMAND" for help on a specific command.`) + + if e != nil { + return nil, e + } + + subcmd := parse.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parse, nil +} + +func showErrorAndExit(e error) { + log.Errorf("%s", e) + os.Exit(1) +} + +func newClient(options *BaseOptions) (*huawei.SRegion, error) { + if len(options.AccessKey) == 0 { + return nil, fmt.Errorf("Missing accessKey") + } + + if len(options.Secret) == 0 { + return nil, fmt.Errorf("Missing secret") + } + + account := "" + if len(options.ProjectId) > 0 { + account = options.AccessKey + "/" + options.ProjectId + } else { + account = options.AccessKey + } + + cli, err := huawei.NewHuaweiClient("", "", options.CloudEnv, account, options.Secret, options.Debug) + if err != nil { + return nil, err + } + + region := cli.GetRegion(options.RegionId) + if region == nil { + return nil, fmt.Errorf("No such region %s", options.RegionId) + } + + return region, nil +} + +func main() { + parser, e := getSubcommandParser() + if e != nil { + showErrorAndExit(e) + } + e = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + } else { + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if e != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(e) + } else { + suboptions := subparser.Options() + if options.SUBCOMMAND == "help" { + e = subcmd.Invoke(suboptions) + } else { + var region *huawei.SRegion + region, e = newClient(options) + if e != nil { + showErrorAndExit(e) + } + e = subcmd.Invoke(region, suboptions) + } + if e != nil { + showErrorAndExit(e) + } + } + } +} diff --git a/cmd/ipmicli/main.go b/cmd/ipmicli/main.go new file mode 100644 index 0000000000..84dbe1701f --- /dev/null +++ b/cmd/ipmicli/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "fmt" + "os" + + "yunion.io/x/log" + "yunion.io/x/structarg" + + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/util/shellutils" + "yunion.io/x/onecloud/pkg/util/ssh" + + _ "yunion.io/x/onecloud/pkg/util/ipmitool/shell" +) + +type BaseOptions struct { + Help bool `help:"Show help" short-token:"h"` + MODE string `help:"Execute command mode" choices:"ssh|rmcp"` + HOST string `help:"IP address of remote host"` + PASSWD string `help:"Password"` + User string `help:"Username" short-token:"u" default:"root"` + Port int `help:"Remote service port"` + SUBCOMMAND string `help:"ipmicli subcommand" subcommand:"true"` +} + +func showErrorAndExit(err error) { + log.Errorf("%s", err) + os.Exit(1) +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parser, err := structarg.NewArgumentParser( + &BaseOptions{}, + "ipmicli", + "Command-line interface to ipmitool", + `See "ipmicli help COMMAND" for help on a specific command.`, + ) + if err != nil { + return nil, err + } + subcmd := parser.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parser, nil +} + +func newExecutor(options *BaseOptions) (ipmitool.IPMIExecutor, error) { + if options.MODE == "ssh" { + port := 22 + if options.Port > 0 { + port = options.Port + } + sshCli, err := ssh.NewClient(options.HOST, port, options.User, options.PASSWD, "") + if err != nil { + return nil, err + } + return ipmitool.NewSSHIPMI(sshCli), nil + } + if options.MODE == "rmcp" { + port := 623 + if options.Port > 0 { + port = options.Port + } + return ipmitool.NewLanPlusIPMIWithPort(options.HOST, options.User, options.PASSWD, port), nil + } + return nil, fmt.Errorf("Unsupported mode: %s", options.MODE) +} + +func main() { + parser, err := getSubcommandParser() + if err != nil { + showErrorAndExit(err) + } + + err = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + return + } + + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if err != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(err) + return + } + + suboptions := subparser.Options() + var args []interface{} + if options.SUBCOMMAND == "help" { + args = append(args, suboptions) + } else { + executor, err := newExecutor(options) + if err != nil { + showErrorAndExit(err) + } + args = append(args, executor, suboptions) + } + err = subcmd.Invoke(args...) + if err != nil { + showErrorAndExit(err) + } +} diff --git a/cmd/logger/main.go b/cmd/logger/main.go new file mode 100644 index 0000000000..bd3af5eae7 --- /dev/null +++ b/cmd/logger/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "yunion.io/x/onecloud/pkg/logger/service" +) + +func main() { + service.StartService() +} diff --git a/pkg/appsrv/appsrv.go b/pkg/appsrv/appsrv.go index 8e9618c0e3..ea4fe3235e 100644 --- a/pkg/appsrv/appsrv.go +++ b/pkg/appsrv/appsrv.go @@ -6,7 +6,6 @@ import ( "math/rand" "net/http" "os" - "os/signal" "strings" "sync" "syscall" @@ -15,6 +14,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/trace" + "yunion.io/x/pkg/util/signalutils" "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/appctx" @@ -40,6 +40,7 @@ type Application struct { cors *Cors middlewares []MiddlewareFunc + isExiting bool idleConnsClosed chan struct{} } @@ -332,44 +333,73 @@ func (app *Application) initServer(addr string) *http.Server { func (app *Application) registerCleanShutdown(s *http.Server, onStop func()) { app.idleConnsClosed = make(chan struct{}) - go func() { - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) - log.Infof("Close signal received: %+v", <-c) + + // dump goroutine stack + signalutils.RegisterSignal(func() { + utils.DumpAllGoroutineStack(log.Logger().Out) + }, syscall.SIGUSR1) + + quitSignals := []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM} + signalutils.RegisterSignal(func() { + if app.isExiting { + log.Infof("Quit signal received!!! clean up in progress, be patient...") + return + } + app.isExiting = true + log.Infof("Quit signal received!!! do cleanup...") + if err := s.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: log.Errorf("HTTP server Shutdown: %v", err) } - onStop() - + if onStop != nil { + func() { + defer func() { + if r := recover(); r != nil { + log.Errorf("app exiting error: %s", r) + } + }() + onStop() + }() + } close(app.idleConnsClosed) - }() + }, quitSignals...) + + signalutils.StartTrap() } func (app *Application) waitCleanShutdown() { <-app.idleConnsClosed + log.Infof("Service stopped.") } -func (app *Application) ListenAndServe(addr string, onStop func()) { +func (app *Application) ListenAndServe(addr string) { + app.ListenAndServeWithCleanup(addr, nil) +} + +func (app *Application) ListenAndServeTLS(addr string, certFile, keyFile string) { + app.ListenAndServeTLSWithCleanup(addr, certFile, keyFile, nil) +} + +func (app *Application) ListenAndServeWithCleanup(addr string, onStop func()) { + app.ListenAndServeTLSWithCleanup(addr, "", "", onStop) +} + +func (app *Application) ListenAndServeTLSWithCleanup(addr string, certFile, keyFile string, onStop func()) { s := app.initServer(addr) app.registerCleanShutdown(s, onStop) - err := s.ListenAndServe() + var err error + if len(certFile) == 0 && len(keyFile) == 0 { + err = s.ListenAndServe() + } else { + err = s.ListenAndServeTLS(certFile, keyFile) + } if err != nil && err != http.ErrServerClosed { log.Fatalf("ListAndServer fail: %s", err) } app.waitCleanShutdown() } -func (app *Application) ListenAndServeTLS(addr string, certFile, keyFile string, onStop func()) { - s := app.initServer(addr) - app.registerCleanShutdown(s, onStop) - err := s.ListenAndServeTLS(certFile, keyFile) - if err != nil && err != http.ErrServerClosed { - log.Fatalf("ListAndServerTLS fail: %s", err) - } - app.waitCleanShutdown() -} - func isJsonContentType(r *http.Request) bool { contType := strings.ToLower(r.Header.Get("Content-Type")) if strings.HasPrefix(contType, "application/json") { diff --git a/pkg/baremetal/agent.go b/pkg/baremetal/agent.go new file mode 100644 index 0000000000..bbcc21a11b --- /dev/null +++ b/pkg/baremetal/agent.go @@ -0,0 +1,399 @@ +package baremetal + +import ( + "context" + "fmt" + "net" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/baremetal/pxe" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +var ( + baremetalAgent *SBaremetalAgent +) + +type SZone struct { + Name string `json:"name"` + Id string `json:"id"` +} + +type SBaremetalAgent struct { + PXEServer *pxe.Server + ListenInterface *net.Interface + AgentId string + AgentName string + Zone *SZone + Manager *SBaremetalManager +} + +func newBaremetalAgent() (*SBaremetalAgent, error) { + iface, err := net.InterfaceByName(o.Options.ListenInterface) + if err != nil { + return nil, err + } + ips, err := getIfaceIPs(iface) + if err != nil { + return nil, err + } + if len(ips) == 0 { + return nil, fmt.Errorf("Interface %s ip address not found", o.Options.ListenInterface) + } + log.Debugf("Interface %s ip address: %v", iface.Name, ips) + + agent := &SBaremetalAgent{ + ListenInterface: iface, + } + return agent, nil +} + +func GetAdminSession() *mcclient.ClientSession { + return auth.GetAdminSession(context.TODO(), o.Options.Region, "v2") +} + +func (agent *SBaremetalAgent) GetListenIPs() ([]net.IP, error) { + ips, err := getIfaceIPs(agent.ListenInterface) + if err != nil { + return nil, err + } + if len(ips) == 0 { + return nil, fmt.Errorf("Interface %s ip address not found", agent.ListenInterface.Name) + } + return ips, nil +} + +func (agent *SBaremetalAgent) GetListenIP() (net.IP, error) { + ips, err := agent.GetListenIPs() + if err != nil { + return nil, err + } + if o.Options.ListenAddress == "" { + return ips[0], nil + } + if o.Options.ListenAddress == "0.0.0.0" { + return net.ParseIP(o.Options.ListenAddress), nil + } + for _, ip := range ips { + if ip.String() == o.Options.ListenAddress { + return ip, nil + } + } + return nil, fmt.Errorf("Not found ListenAddress %s on %s", o.Options.ListenAddress, o.Options.ListenInterface) +} + +func (agent *SBaremetalAgent) GetAccessIP() (net.IP, error) { + ips, err := agent.GetListenIPs() + if err != nil { + return nil, err + } + if o.Options.AccessAddress == "" { + return ips[0], nil + } + for _, ip := range ips { + if ip.String() == o.Options.AccessAddress { + return ip, nil + } + } + return nil, fmt.Errorf("Not found AccessAddress %s on %s", o.Options.AccessAddress, o.Options.ListenInterface) +} + +func (agent *SBaremetalAgent) GetDHCPServerIP() (net.IP, error) { + listenIP := o.Options.ListenAddress + if len(listenIP) == 0 || listenIP == "0.0.0.0" { + return agent.GetAccessIP() + } + return agent.GetListenIP() +} + +func getIfaceIPs(iface *net.Interface) ([]net.IP, error) { + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + ips := make([]net.IP, 0) + for _, a := range addrs { + if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + ips = append(ips, ipnet.IP) + } + } + } + return ips, nil +} + +func (agent *SBaremetalAgent) startRegister() { + if agent.AgentId != "" { + return + } + + var delayRetryTime time.Duration = 30 * time.Second + + for { + err := agent.register() + if err != nil { + log.Errorf("Register error: %v, retry after %s...", err, delayRetryTime) + time.Sleep(delayRetryTime) + continue + } + break + } + return +} + +func (agent *SBaremetalAgent) register() error { + session := GetAdminSession() + var err error + err = agent.fetchZone(session) + if err != nil { + return err + } + err = agent.createOrUpdateBaremetalAgent(session) + if err != nil { + return err + } + log.Infof("Baremetal %s:%s register success, do offline", agent.AgentName, agent.AgentId) + err = agent.doOffline(session) + if err != nil { + return err + } + + agent.tuneSystem() + + manager, err := NewBaremetalManager(agent) + if err != nil { + return fmt.Errorf("New baremetal manager error: %v", err) + } + + err = manager.loadConfigs() + if err != nil { + return fmt.Errorf("Baremetal manager load config error: %v", err) + } + + agent.Manager = manager + agent.startPXEServices(manager) + return nil +} + +func (agent *SBaremetalAgent) GetManager() *SBaremetalManager { + return agent.Manager +} + +func (agent *SBaremetalAgent) getZoneByIP(session *mcclient.ClientSession) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + listenIP, err := agent.GetListenIP() + if err != nil { + return nil, err + } + params.Add(jsonutils.NewString(listenIP.String()), "ip") + networks, err := modules.Networks.List(session, params) + if err != nil { + return nil, err + } + if len(networks.Data) == 0 { + return nil, fmt.Errorf("Not found networks by agent listen ip: %s", listenIP) + } + wireId, err := networks.Data[0].GetString("wire_id") + if err != nil { + return nil, err + } + wire, err := modules.Wires.Get(session, wireId, nil) + if err != nil { + return nil, err + } + zoneId, err := wire.GetString("zone_id") + if err != nil { + return nil, err + } + + zone, err := modules.Zones.Get(session, zoneId, nil) + if err != nil { + return nil, err + } + return zone, nil +} + +func (agent *SBaremetalAgent) fetchZone(session *mcclient.ClientSession) error { + zoneName := o.Options.Zone + var zoneInfoObj jsonutils.JSONObject + var err error + if zoneName != "" { + zoneInfoObj, err = modules.Zones.Get(session, zoneName, nil) + } else { + zoneInfoObj, err = agent.getZoneByIP(session) + } + if err != nil { + return err + } + zone := SZone{} + err = zoneInfoObj.Unmarshal(&zone) + if err != nil { + return err + } + agent.Zone = &zone + return nil +} + +func (agent *SBaremetalAgent) createOrUpdateBaremetalAgent(session *mcclient.ClientSession) error { + params := jsonutils.NewDict() + naccessIP, err := agent.GetAccessIP() + if err != nil { + return err + } + params.Add(jsonutils.NewString(naccessIP.String()), "access_ip") + ret, err := modules.Baremetalagents.List(session, params) + if err != nil { + return err + } + var ( + cloudObj jsonutils.JSONObject + agentId string + agentName string + ) + // create or update BaremetalAgent + if len(ret.Data) == 0 { + cloudObj, err = agent.createBaremetalAgent(session) + if err != nil { + return err + } + } else { + cloudBmAgent := ret.Data[0] + accessIP, _ := cloudBmAgent.GetString("access_ip") + managerUri, _ := cloudBmAgent.GetString("manager_uri") + zoneId, _ := cloudBmAgent.GetString("zone_id") + agentId, _ := cloudBmAgent.GetString("id") + if naccessIP.String() != accessIP || + agent.GetManagerUri() != managerUri || + zoneId != agent.Zone.Id { + cloudObj, err = agent.updateBaremetalAgent(session, agentId) + if err != nil { + return err + } + } else { + cloudObj = cloudBmAgent + } + } + + agentId, err = cloudObj.GetString("id") + if err != nil { + return err + } + agentName, err = cloudObj.GetString("name") + if err != nil { + return err + } + + agent.AgentId = agentId + agent.AgentName = agentName + return nil +} + +func (agent *SBaremetalAgent) GetManagerUri() string { + accessIP, _ := agent.GetAccessIP() + proto := "http" + if o.Options.EnableSsl { + proto = "https" + } + return fmt.Sprintf("%s://%s:%d", proto, accessIP, o.Options.Port) +} + +func (agent *SBaremetalAgent) getCreateUpdateInfo() (jsonutils.JSONObject, error) { + accessIP, err := agent.GetAccessIP() + if err != nil { + return nil, err + } + params := jsonutils.NewDict() + if agent.AgentId == "" { + params.Add(jsonutils.NewString(fmt.Sprintf("baremetal_%s", accessIP)), "name") + } + params.Add(jsonutils.NewString(accessIP.String()), "access_ip") + params.Add(jsonutils.NewString(agent.GetManagerUri()), "manager_uri") + params.Add(jsonutils.NewString(agent.Zone.Id), "zone_id") + return params, nil +} + +func (agent *SBaremetalAgent) createBaremetalAgent(session *mcclient.ClientSession) (jsonutils.JSONObject, error) { + params, err := agent.getCreateUpdateInfo() + if err != nil { + return nil, err + } + return modules.Baremetalagents.Create(session, params) +} + +func (agent *SBaremetalAgent) updateBaremetalAgent(session *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) { + params, err := agent.getCreateUpdateInfo() + if err != nil { + return nil, err + } + return modules.Baremetalagents.Update(session, id, params) +} + +func (agent *SBaremetalAgent) doOffline(session *mcclient.ClientSession) error { + _, err := modules.Baremetalagents.PerformAction(session, agent.AgentId, "offline", nil) + return err +} + +func (agent *SBaremetalAgent) doOnline(session *mcclient.ClientSession) error { + _, err := modules.Baremetalagents.PerformAction(session, agent.AgentId, "online", nil) + return err +} + +func (agent *SBaremetalAgent) tuneSystem() { + agent.disableUDPOffloading() +} + +func (agent *SBaremetalAgent) disableUDPOffloading() { + log.Infof("Disable UDP offloading") + offTx := procutils.NewCommand("ethtool", "--offload", o.Options.ListenInterface, "tx", "off") + offTx.Run() + offGso := procutils.NewCommand("ethtool", "-K", o.Options.ListenInterface, "gso", "off") + offGso.Run() +} + +func (agent *SBaremetalAgent) startPXEServices(manager *SBaremetalManager) { + listenIP, err := agent.GetListenIP() + if err != nil { + log.Fatalf("Get listen ip address error: %v", err) + } + agent.PXEServer = &pxe.Server{ + TFTPRootDir: o.Options.TftpRoot, + Address: listenIP.String(), + BaremetalManager: manager, + } + go func() { + err := agent.PXEServer.Serve() + if err != nil { + log.Fatalf("Start PXE server error: %v", err) + } + }() +} + +func Start() error { + var err error + if baremetalAgent != nil { + log.Warningf("Global baremetalAgent already start") + return nil + } + baremetalAgent, err = newBaremetalAgent() + if err != nil { + return err + } + baremetalAgent.startRegister() + + return nil +} + +func GetBaremetalAgent() *SBaremetalAgent { + return baremetalAgent +} + +func GetBaremetalManager() *SBaremetalManager { + return GetBaremetalAgent().GetManager() +} diff --git a/pkg/baremetal/doc.go b/pkg/baremetal/doc.go new file mode 100644 index 0000000000..c42c883361 --- /dev/null +++ b/pkg/baremetal/doc.go @@ -0,0 +1 @@ +package baremetal // import "yunion.io/x/onecloud/pkg/baremetal" diff --git a/pkg/baremetal/handler/delay.go b/pkg/baremetal/handler/delay.go new file mode 100644 index 0000000000..b1f3b221f0 --- /dev/null +++ b/pkg/baremetal/handler/delay.go @@ -0,0 +1,48 @@ +package handler + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var delayTaskWorkerMan *appsrv.SWorkerManager + +func init() { + delayTaskWorkerMan = appsrv.NewWorkerManager("DelayTaskWorkerManager", 8, 1024, false) +} + +type ProcessFunc func(data jsonutils.JSONObject) (jsonutils.JSONObject, error) + +type delayTask struct { + process ProcessFunc + taskId string + session *mcclient.ClientSession + data jsonutils.JSONObject +} + +func newDelayTask(process ProcessFunc, session *mcclient.ClientSession, taskId string, data jsonutils.JSONObject) *delayTask { + return &delayTask{ + process: process, + taskId: taskId, + session: session, + data: data, + } +} + +func DelayProcess(process ProcessFunc, session *mcclient.ClientSession, taskId string, data jsonutils.JSONObject) { + delayTaskWorkerMan.Run(func() { + executeDelayProcess(newDelayTask(process, session, taskId, data)) + }, nil, nil) +} + +func executeDelayProcess(task *delayTask) { + ret, err := task.process(task.data) + if err != nil { + modules.ComputeTasks.TaskFailed(task.session, task.taskId, err) + return + } + modules.ComputeTasks.TaskComplete(task.session, task.taskId, ret) +} diff --git a/pkg/baremetal/handler/doc.go b/pkg/baremetal/handler/doc.go new file mode 100644 index 0000000000..3a787f6375 --- /dev/null +++ b/pkg/baremetal/handler/doc.go @@ -0,0 +1 @@ +package handler // import "yunion.io/x/onecloud/pkg/baremetal/handler" diff --git a/pkg/baremetal/handler/handlers.go b/pkg/baremetal/handler/handlers.go new file mode 100644 index 0000000000..9110077153 --- /dev/null +++ b/pkg/baremetal/handler/handlers.go @@ -0,0 +1,153 @@ +package handler + +import ( + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/baremetal" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/httperrors" +) + +func InitHandlers(app *appsrv.Application) { + initBaremetalsHandler(app) +} + +func initBaremetalsHandler(app *appsrv.Application) { + // baremetal actions handler + app.AddHandler("GET", bmActionPrefix("notify"), bmObjMiddleware(handleBaremetalNotify)) + app.AddHandler("POST", bmActionPrefix("maintenance"), bmObjMiddleware(handleBaremetalMaintenance)) + app.AddHandler("POST", bmActionPrefix("unmaintenance"), bmObjMiddleware(handleBaremetalUnmaintenance)) + app.AddHandler("POST", bmActionPrefix("delete"), bmObjMiddleware(handleBaremetalDelete)) + app.AddHandler("POST", bmActionPrefix("syncstatus"), bmObjMiddleware(handleBaremetalSyncStatus)) + app.AddHandler("POST", bmActionPrefix("sync-config"), bmObjMiddleware(handleBaremetalSyncConfig)) + app.AddHandler("POST", bmActionPrefix("sync-ipmi"), bmObjMiddleware(handleBaremetalSyncIPMI)) + app.AddHandler("POST", bmActionPrefix("prepare"), bmObjMiddleware(handleBaremetalPrepare)) + app.AddHandler("POST", bmActionPrefix("reset-bmc"), bmObjMiddleware(handleBaremetalResetBMC)) + + // server actions handler + app.AddHandler("POST", srvActionPrefix("create"), srvClassMiddleware(handleServerCreate)) + app.AddHandler("POST", srvActionPrefix("deploy"), srvObjMiddleware(handleServerDeploy)) + app.AddHandler("POST", srvActionPrefix("rebuild"), srvObjMiddleware(handleServerRebuild)) + app.AddHandler("POST", srvActionPrefix("start"), srvObjMiddleware(handleServerStart)) + app.AddHandler("POST", srvActionPrefix("stop"), srvObjMiddleware(handleServerStop)) + app.AddHandler("POST", srvActionPrefix("reset"), srvObjMiddleware(handleServerReset)) + app.AddHandler("POST", srvActionPrefix("status"), srvObjMiddleware(handleServerStatus)) + app.AddHandler("DELETE", srvIdPrefix(), srvObjMiddleware(handleServerDelete)) +} + +func handleBaremetalNotify(ctx *Context, bm *baremetal.SBaremetalInstance) { + key, err := ctx.Query().GetString("key") + if err != nil { + ctx.ResponseError(httperrors.NewInputParameterError("Not found key in query")) + return + } + remoteAddr := ctx.RequestRemoteIP() + err = bm.SaveSSHConfig(remoteAddr, key) + if err != nil { + log.Errorf("Save baremetal %s ssh config: %v", bm.GetId(), err) + } + + // execute BaremetalServerPrepareTask + task := bm.GetTask() + if task != nil { + task.SSHExecute(task, remoteAddr, key, nil) + } + ctx.ResponseOk() +} + +func handleBaremetalMaintenance(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalMaintenanceTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} + +func handleBaremetalUnmaintenance(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalUnmaintenanceTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} + +func handleBaremetalDelete(ctx *Context, bm *baremetal.SBaremetalInstance) { + ctx.DelayProcess(bm.DelayedRemove, nil) + ctx.ResponseOk() +} + +func handleBaremetalSyncStatus(ctx *Context, bm *baremetal.SBaremetalInstance) { + ctx.DelayProcess(bm.DelayedSyncStatus, nil) + ctx.ResponseOk() +} + +func handleBaremetalSyncConfig(ctx *Context, bm *baremetal.SBaremetalInstance) { + ctx.DelayProcess(bm.DelayedSyncDesc, nil) + ctx.ResponseOk() +} + +func handleBaremetalSyncIPMI(ctx *Context, bm *baremetal.SBaremetalInstance) { + ctx.DelayProcess(bm.DelayedSyncIPMIInfo, nil) + ctx.ResponseOk() +} + +func handleBaremetalPrepare(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalReprepareTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} + +func handleBaremetalResetBMC(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalResetBMCTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} + +func handleServerCreate(ctx *Context, bm *baremetal.SBaremetalInstance) { + err := bm.StartServerCreateTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + if err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerDelete(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + bm.StartServerDestroyTask(ctx.UserCred(), ctx.TaskId(), nil) + ctx.ResponseOk() +} + +func handleServerDeploy(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerDeployTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerRebuild(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerRebuildTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerStart(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerStartTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerStop(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerStopTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerReset(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + ctx.DelayProcess(bm.DelayedServerReset, nil) + ctx.ResponseOk() +} + +func handleServerStatus(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + ctx.DelayProcess(bm.DelayedServerStatus, nil) + ctx.ResponseOk() +} diff --git a/pkg/baremetal/handler/middleware.go b/pkg/baremetal/handler/middleware.go new file mode 100644 index 0000000000..91e132a973 --- /dev/null +++ b/pkg/baremetal/handler/middleware.go @@ -0,0 +1,186 @@ +package handler + +import ( + "context" + "fmt" + "net/http" + "strings" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/baremetal" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" +) + +const ( + BM_PREFIX = "baremetals" + SERVER_PREFIX = "servers" + + PARAMS_BMID_KEY = "" + PARAMS_SRVID_KEY = "" +) + +func bmIdPrefix() string { + // baremetals/ + return fmt.Sprintf("%s/%s", BM_PREFIX, PARAMS_BMID_KEY) +} + +func bmActionPrefix(action string) string { + // baremetals//action + return fmt.Sprintf("%s/%s", bmIdPrefix(), action) +} + +func srvIdPrefix() string { + // baremetals//servers/ + return fmt.Sprintf("%s/%s/%s", bmIdPrefix(), SERVER_PREFIX, PARAMS_SRVID_KEY) +} + +func srvActionPrefix(action string) string { + // baremetals//servers//action + return fmt.Sprintf("%s/%s", srvIdPrefix(), action) +} + +type handlerFunc func(ctx *Context) + +func authMiddleware(h handlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + h(newCtx) + } +} + +type bmObjHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance) + +func bmObjMiddleware(h bmObjHandlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + bmId := newCtx.Params()[PARAMS_BMID_KEY] + baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) + if baremetal == nil { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) + return + } + h(newCtx, baremetal) + } +} + +type srvObjHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance, srv baremetaltypes.IBaremetalServer) + +func srvClassMiddleware(h bmObjHandlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + bmId := newCtx.Params()[PARAMS_BMID_KEY] + //srvId := newCtx.Params()[PARAMS_SRVID_KEY] + baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) + if baremetal == nil { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) + return + } + if baremetal.GetServerId() != "" { + newCtx.ResponseError(httperrors.NewNotAcceptableError("Baremetal %s occupied", bmId)) + return + } + h(newCtx, baremetal) + } +} + +func srvObjMiddleware(h srvObjHandlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + bmId := newCtx.Params()[PARAMS_BMID_KEY] + srvId := newCtx.Params()[PARAMS_SRVID_KEY] + baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) + if baremetal == nil { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) + return + } + if baremetal.GetServerId() != srvId { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found server by id: %s", srvId)) + return + } + srv := baremetal.GetServer() + h(newCtx, baremetal, srv) + } +} + +type Context struct { + context.Context + userCred mcclient.TokenCredential + params map[string]string + query jsonutils.JSONObject + data jsonutils.JSONObject + request *http.Request + writer http.ResponseWriter +} + +func NewContext(ctx context.Context, w http.ResponseWriter, r *http.Request) *Context { + params, query, body := appsrv.FetchEnv(ctx, w, r) + return &Context{ + Context: ctx, + userCred: auth.FetchUserCredential(ctx, nil), + params: params, + query: query, + data: body, + request: r, + writer: w, + } +} + +func (ctx *Context) Params() map[string]string { + return ctx.params +} + +func (ctx *Context) Data() jsonutils.JSONObject { + return ctx.data +} + +func (ctx *Context) Query() jsonutils.JSONObject { + return ctx.query +} + +func (ctx *Context) UserCred() mcclient.TokenCredential { + return ctx.userCred +} + +func (ctx *Context) TaskId() string { + return ctx.Request().Header.Get(mcclient.TASK_ID) +} + +func (ctx *Context) ResponseStruct(obj interface{}) { + appsrv.SendStruct(ctx.writer, obj) +} + +func (ctx *Context) ResponseJson(obj jsonutils.JSONObject) { + appsrv.SendJSON(ctx.writer, obj) +} + +func (ctx *Context) ResponseError(err error) { + httperrors.GeneralServerError(ctx.writer, err) +} + +func (ctx *Context) Request() *http.Request { + return ctx.request +} + +func (ctx *Context) RequestRemoteIP() string { + remoteAddr := ctx.Request().RemoteAddr + return strings.Split(remoteAddr, ":")[0] +} + +func (ctx *Context) ResponseOk() { + obj := jsonutils.NewDict() + obj.Add(jsonutils.NewString("ok"), "result") + appsrv.SendJSON(ctx.writer, obj) +} + +func (ctx *Context) GetBaremetalManager() *baremetal.SBaremetalManager { + return baremetal.GetBaremetalManager() +} + +func (ctx *Context) DelayProcess(process ProcessFunc, data jsonutils.JSONObject) { + DelayProcess(process, ctx.GetBaremetalManager().GetClientSession(), ctx.TaskId(), data) +} diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go new file mode 100644 index 0000000000..d2b119eedd --- /dev/null +++ b/pkg/baremetal/manager.go @@ -0,0 +1,1475 @@ +package baremetal + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/errors" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/seclib" + "yunion.io/x/pkg/util/sets" + "yunion.io/x/pkg/util/workqueue" + "yunion.io/x/pkg/utils" + + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/baremetal/profiles" + "yunion.io/x/onecloud/pkg/baremetal/pxe" + baremetalstatus "yunion.io/x/onecloud/pkg/baremetal/status" + "yunion.io/x/onecloud/pkg/baremetal/tasks" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/baremetal/utils/detect_storages" + "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + raiddrivers "yunion.io/x/onecloud/pkg/baremetal/utils/raid/drivers" + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/guestfs/sshpart" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/dhcp" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalManager struct { + Agent *SBaremetalAgent + configPath string + baremetals *sBaremetalMap +} + +func NewBaremetalManager(agent *SBaremetalAgent) (*SBaremetalManager, error) { + bmPaths := o.Options.BaremetalsPath + err := os.MkdirAll(bmPaths, 0755) + if err != nil { + return nil, err + } + return &SBaremetalManager{ + Agent: agent, + configPath: bmPaths, + baremetals: newBaremetalMap(), + }, nil +} + +func (m *SBaremetalManager) killAllIPMITool() { + procutils.NewCommand("killall", "-9", "ipmitool").Run() +} + +func (m *SBaremetalManager) GetClientSession() *mcclient.ClientSession { + return GetAdminSession() +} + +func (m *SBaremetalManager) GetZoneId() string { + return m.Agent.Zone.Id +} + +func (m *SBaremetalManager) loadConfigs() error { + m.killAllIPMITool() + files, err := ioutil.ReadDir(m.configPath) + if err != nil { + return err + } + bmIds := make([]string, 0) + for _, file := range files { + if file.IsDir() && regutils.MatchUUID(file.Name()) { + bmIds = append(bmIds, file.Name()) + } + } + + session := m.GetClientSession() + errsChannel := make(chan error, len(bmIds)) + initBaremetal := func(i int) { + bmId := bmIds[i] + err := m.initBaremetal(session, bmId) + if err != nil { + errsChannel <- err + return + } + } + workqueue.Parallelize(4, len(bmIds), initBaremetal) + errs := make([]error, 0) + if len(errsChannel) > 0 { + length := len(errsChannel) + for ; length > 0; length-- { + errs = append(errs, <-errsChannel) + } + } + return errors.NewAggregate(errs) +} + +func (m *SBaremetalManager) initBaremetal(session *mcclient.ClientSession, bmId string) error { + desc, err := m.updateBaremetal(session, bmId) + if err != nil { + return err + } + bmInstance, err := m.AddBaremetal(desc) + if err != nil { + return err + } + bmObj := bmInstance.(*SBaremetalInstance) + if !sets.NewString(INIT, PREPARE, UNKNOWN).Has(bmObj.GetStatus()) { + bmObj.SyncStatusBackground() + } + return nil +} + +func (m *SBaremetalManager) CleanBaremetal(bmId string) { + bm := m.baremetals.Pop(bmId) + if bm != nil { + bm.Stop() + } + path := bm.GetDir() + procutils.NewCommand("rm", "-fr", path).Run() +} + +func (m *SBaremetalManager) updateBaremetal(session *mcclient.ClientSession, bmId string) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.JSONTrue, "is_baremetal") + obj, err := modules.Hosts.Put(session, bmId, params) + if err != nil { + return nil, err + } + log.Infof("Baremetal %s update success", bmId) + return obj, nil +} + +func (m *SBaremetalManager) AddBaremetal(desc jsonutils.JSONObject) (pxe.IBaremetalInstance, error) { + id, err := desc.GetString("id") + if err != nil { + return nil, fmt.Errorf("Not found baremetal id in desc %s", desc) + } + if instance, ok := m.baremetals.Get(id); ok { + return instance, instance.SaveDesc(desc) + } + bm, err := newBaremetalInstance(m, desc) + if err != nil { + return nil, err + } + m.baremetals.Add(bm) + return bm, nil +} + +func (m *SBaremetalManager) GetBaremetals() []*SBaremetalInstance { + objs := make([]*SBaremetalInstance, 0) + getter := func(key, val interface{}) bool { + objs = append(objs, val.(*SBaremetalInstance)) + return true + } + m.baremetals.Range(getter) + return objs +} + +func (m *SBaremetalManager) GetBaremetalById(bmId string) *SBaremetalInstance { + obj, _ := m.baremetals.Get(bmId) + return obj +} + +func (m *SBaremetalManager) GetBaremetalByMac(mac net.HardwareAddr) pxe.IBaremetalInstance { + var obj *SBaremetalInstance + getter := func(key, val interface{}) bool { + instance := val.(*SBaremetalInstance) + if instance.GetNicByMac(mac) != nil { + obj = instance + // stop the iteration + return false + } + // not found, continue iteration + return true + } + m.baremetals.Range(getter) + return obj +} + +func (m *SBaremetalManager) Stop() { + for _, bm := range m.GetBaremetals() { + bm.Stop() + } +} + +type sBaremetalMap struct { + *sync.Map +} + +func newBaremetalMap() *sBaremetalMap { + return &sBaremetalMap{ + Map: new(sync.Map), + } +} + +func (m *sBaremetalMap) Add(bm *SBaremetalInstance) { + m.Store(bm.GetId(), bm) +} + +func (m *sBaremetalMap) Get(id string) (*SBaremetalInstance, bool) { + obj, ok := m.Load(id) + if !ok { + return nil, false + } + return obj.(*SBaremetalInstance), true +} + +func (m *sBaremetalMap) Delete(id string) { + m.Map.Delete(id) +} + +func (m *sBaremetalMap) Pop(id string) *SBaremetalInstance { + obj, exist := m.Get(id) + if exist { + m.Delete(id) + } + return obj +} + +type SBaremetalInstance struct { + manager *SBaremetalManager + desc *jsonutils.JSONDict + descLock *sync.Mutex + taskQueue *tasks.TaskQueue + server baremetaltypes.IBaremetalServer + serverLock *sync.Mutex +} + +func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*SBaremetalInstance, error) { + bm := &SBaremetalInstance{ + manager: man, + desc: desc.(*jsonutils.JSONDict), + descLock: new(sync.Mutex), + taskQueue: tasks.NewTaskQueue(), + serverLock: new(sync.Mutex), + } + err := os.MkdirAll(bm.GetDir(), 0755) + if err != nil { + return nil, err + } + err = bm.SaveDesc(desc) + if err != nil { + return nil, err + } + bm.loadServer() + return bm, nil +} + +func (b *SBaremetalInstance) GetClientSession() *mcclient.ClientSession { + return b.manager.GetClientSession() +} + +func (b *SBaremetalInstance) GetId() string { + id, err := b.desc.GetString("id") + if err != nil { + log.Fatalf("Get id from desc error: %v", err) + } + return id +} + +func (b *SBaremetalInstance) GetName() string { + id, err := b.desc.GetString("name") + if err != nil { + log.Fatalf("Get name from desc error: %v", err) + } + return id +} + +func (b *SBaremetalInstance) Stop() { + // TODO: +} + +func (b *SBaremetalInstance) GetDir() string { + return filepath.Join(b.manager.configPath, b.GetId()) +} + +func (b *SBaremetalInstance) GetDescFilePath() string { + return filepath.Join(b.GetDir(), "desc") +} + +func (b *SBaremetalInstance) GetServerDescFilePath() string { + return filepath.Join(b.GetDir(), "server") +} + +func (b *SBaremetalInstance) GetSSHConfigFilePath() string { + return filepath.Join(b.GetDir(), "ssh") +} + +func (b *SBaremetalInstance) GetStatus() string { + status, err := b.desc.GetString("status") + if err != nil { + log.Fatalf("Get status from desc error: %v", err) + } + return status +} + +func (b *SBaremetalInstance) AutoSaveDesc() error { + return b.SaveDesc(nil) +} + +func (b *SBaremetalInstance) SaveDesc(desc jsonutils.JSONObject) error { + b.descLock.Lock() + defer b.descLock.Unlock() + if desc != nil { + b.desc = desc.(*jsonutils.JSONDict) + } + return ioutil.WriteFile(b.GetDescFilePath(), []byte(b.desc.String()), 0644) +} + +func (b *SBaremetalInstance) loadServer() { + b.serverLock.Lock() + defer b.serverLock.Unlock() + if !b.desc.Contains("server_id") { + return + } + descPath := b.GetServerDescFilePath() + desc, err := ioutil.ReadFile(descPath) + if err != nil { + log.Errorf("Failed to read server desc %s: %v", descPath, err) + return + } + descObj, err := jsonutils.Parse(desc) + if err != nil { + log.Errorf("Failed to parse server json string: %v", err) + return + } + srv, err := newBaremetalServer(b, descObj.(*jsonutils.JSONDict)) + if err != nil { + log.Errorf("New server error: %v", err) + return + } + if bmSrvId, _ := b.desc.GetString("server_id"); srv.GetId() != bmSrvId { + log.Errorf("Server id %q not equal baremetal %q server id %q", srv.GetId(), b.GetName(), bmSrvId) + return + } + b.server = srv +} + +func (b *SBaremetalInstance) SaveSSHConfig(remoteAddr string, key string) error { + var err error + key, err = utils.EncryptAESBase64(b.GetId(), key) + if err != nil { + return err + } + sshConf := types.SSHConfig{ + Username: "root", + Password: key, + RemoteIP: remoteAddr, + } + conf := jsonutils.Marshal(sshConf) + err = ioutil.WriteFile(b.GetSSHConfigFilePath(), []byte(conf.String()), 0644) + if err != nil { + return err + } + b.SyncSSHConfig(sshConf) + return err +} + +func (b *SBaremetalInstance) GetSSHConfig() (*types.SSHConfig, error) { + path := b.GetSSHConfigFilePath() + content, err := ioutil.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + conf := types.SSHConfig{} + obj, err := jsonutils.Parse(content) + if err != nil { + return nil, err + } + err = obj.Unmarshal(&conf) + if err != nil { + return nil, err + } + conf.Password, err = utils.DescryptAESBase64(b.GetId(), conf.Password) + if err != nil { + return nil, err + } + return &conf, nil +} + +func (b *SBaremetalInstance) TestSSHConfig() bool { + conf, err := b.GetSSHConfig() + if err != nil { + return false + } + if conf == nil { + return false + } + sshCli, err := ssh.NewClient(conf.RemoteIP, 22, "root", conf.Password, "") + if err != nil { + return false + } + ret, err := sshCli.Run("whoami") + if err != nil { + return false + } + if strings.Contains(strings.Join(ret, ""), "root") { + return true + } + return false +} + +func (b *SBaremetalInstance) ClearSSHConfig() { + path := b.GetSSHConfigFilePath() + err := os.Remove(path) + if err != nil { + log.V(2).Warningf("Clear ssh config %s error: %v", path, err) + } + emptyConfig := types.SSHConfig{ + Username: "None", + Password: "None", + RemoteIP: "None", + } + err = b.SyncSSHConfig(emptyConfig) + if err != nil { + log.Errorf("Sync emtpy SSH config error: %v", err) + } +} + +func (b *SBaremetalInstance) SyncSSHConfig(conf types.SSHConfig) error { + session := b.manager.GetClientSession() + var err error + // encrypt twice + conf.Password, err = utils.EncryptAESBase64(b.GetId(), conf.Password) + if err != nil { + return err + } + data := jsonutils.Marshal(conf) + _, err = modules.Hosts.SetMetadata(session, b.GetId(), data) + return err +} + +func (b *SBaremetalInstance) SyncStatusBackground() { + go func() { + b.AutoSyncAllStatus() + }() +} + +func PowerStatusToBaremetalStatus(status string) string { + switch status { + case types.POWER_STATUS_ON: + return baremetalstatus.RUNNING + case types.POWER_STATUS_OFF: + return baremetalstatus.READY + } + return baremetalstatus.UNKNOWN +} + +func PowerStatusToServerStatus(bm *SBaremetalInstance, status string) string { + switch status { + case types.POWER_STATUS_ON: + if conf, _ := bm.GetSSHConfig(); conf == nil { + return baremetalstatus.SERVER_RUNNING + } else { + return baremetalstatus.SERVER_ADMIN + } + case types.POWER_STATUS_OFF: + return baremetalstatus.SERVER_READY + } + return baremetalstatus.UNKNOWN +} + +func (b *SBaremetalInstance) AutoSyncStatus() { + b.SyncStatus("", "") +} + +func (b *SBaremetalInstance) SyncStatus(status string, reason string) { + if status == "" { + powerStatus, err := b.GetPowerStatus() + if err != nil { + log.Errorf("Get power status error: %v", err) + } + status = PowerStatusToBaremetalStatus(powerStatus) + } + b.desc.Set("status", jsonutils.NewString(status)) + b.AutoSaveDesc() + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(status), "status") + if reason != "" { + params.Add(jsonutils.NewString(reason), "reason") + } + _, err := modules.Hosts.PerformAction(b.GetClientSession(), b.GetId(), "status", params) + if err != nil { + log.Errorf("Update baremetal %s status %s error: %v", b.GetId(), status, err) + return + } + log.Infof("Update baremetal %s to status %s", b.GetId(), status) +} + +func (b *SBaremetalInstance) AutoSyncAllStatus() { + b.SyncAllStatus("") +} + +func (b *SBaremetalInstance) DelayedSyncStatus(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) { + b.AutoSyncAllStatus() + return nil, nil +} + +func (b *SBaremetalInstance) SyncAllStatus(status string) { + var err error + if status == "" { + status, err = b.GetPowerStatus() + if err != nil { + log.Errorf("Get power status error: %v", err) + } + } + b.SyncStatus(PowerStatusToBaremetalStatus(status), "") + b.SyncServerStatus(PowerStatusToServerStatus(b, status)) +} + +func (b *SBaremetalInstance) SyncServerStatus(status string) { + if b.GetServerId() == "" { + return + } + if status == "" { + powerStatus, err := b.GetPowerStatus() + if err != nil { + log.Errorf("Get power status error: %v", err) + } + status = PowerStatusToServerStatus(b, powerStatus) + } + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(status), "status") + _, err := modules.Servers.PerformAction(b.GetClientSession(), b.GetServerId(), "status", params) + if err != nil { + log.Errorf("Update server %s status %s error: %v", b.GetServerName(), status, err) + return + } + log.Infof("Update server %s to status %s", b.GetServerName(), status) +} + +func (b *SBaremetalInstance) getNics() []types.SNic { + nics := []types.SNic{} + err := b.desc.Unmarshal(&nics, "nic_info") + if err != nil { + log.Errorf("Unmarshal desc to get nics error: %v", err) + return nil + } + return nics +} + +func (b *SBaremetalInstance) getNicByType(nicType string) *types.SNic { + nics := b.getNics() + if len(nics) == 0 { + return nil + } + for _, nic := range nics { + tmp := nic + if tmp.Type == nicType { + return &tmp + } + } + return nil +} + +func (b *SBaremetalInstance) GetNicByMac(mac net.HardwareAddr) *types.SNic { + nics := b.getNics() + if len(nics) == 0 { + return nil + } + for _, nic := range nics { + tmp := nic + if tmp.Mac == mac.String() { + return &tmp + } + } + return nil +} + +func (b *SBaremetalInstance) GetAdminNic() *types.SNic { + return b.getNicByType(NIC_TYPE_ADMIN) +} + +func (b *SBaremetalInstance) NeedPXEBoot() bool { + task := b.GetTask() + taskName := "nil" + serverId := b.GetServerId() + if task != nil { + taskName = task.GetName() + } + taskNeedPXEBoot := false + if task != nil && task.NeedPXEBoot() { + taskNeedPXEBoot = true + } + ret := false + if taskNeedPXEBoot || (task == nil && len(serverId) == 0) { + ret = true + } + log.Infof("Check task %s, server %s NeedPXEBoot: %v", taskName, serverId, ret) + return ret +} + +func (b *SBaremetalInstance) GetIPMINic(cliMac net.HardwareAddr) *types.SNic { + nic := b.getNicByType(types.NIC_TYPE_IPMI) + if nic == nil { + return nil + } + if nic.Mac == cliMac.String() { + return nic + } + return nil +} + +func (b *SBaremetalInstance) GetIPMINicIPAddr() string { + nic := b.getNicByType(types.NIC_TYPE_IPMI) + if nic == nil { + return "" + } + return nic.IpAddr +} + +func (b *SBaremetalInstance) GetDHCPConfig(cliMac net.HardwareAddr) (*dhcp.ResponseConfig, error) { + var nic *types.SNic + var hostname string + if b.GetServer() != nil && (b.GetTask() == nil || !b.GetTask().NeedPXEBoot()) { + nic = b.GetServer().GetNicByMac(cliMac) + hostname = b.GetServer().GetName() + } else { + nic = b.GetNicByMac(cliMac) + } + if nic == nil { + return nil, fmt.Errorf("GetNicDHCPConfig no nic found by mac: %s", cliMac) + } + return b.getDHCPConfig(nic, hostname, false, 0) +} + +func (b *SBaremetalInstance) GetPXEDHCPConfig(arch uint16) (*dhcp.ResponseConfig, error) { + return b.getDHCPConfig(b.GetAdminNic(), "", true, arch) +} + +func (b *SBaremetalInstance) getDHCPConfig( + nic *types.SNic, + hostName string, + isPxe bool, + arch uint16, +) (*dhcp.ResponseConfig, error) { + if hostName == "" { + hostName = b.GetName() + } + serverIP, err := b.manager.Agent.GetDHCPServerIP() + if err != nil { + return nil, err + } + return GetNicDHCPConfig(nic, serverIP.String(), hostName, isPxe, arch) +} + +func (b *SBaremetalInstance) GetNotifyUrl() string { + return fmt.Sprintf("%s/baremetals/%s/notify", b.manager.Agent.GetManagerUri(), b.GetId()) +} + +func (b *SBaremetalInstance) GetTFTPResponse() string { + return fmt.Sprintf( + `default start +serial 1 115200 +label start + menu label ^Start + menu default + kernel kernel + append initrd=initramfs token=%s url=%s`, + auth.GetTokenString(), b.GetNotifyUrl()) +} + +func (b *SBaremetalInstance) GetTaskQueue() *tasks.TaskQueue { + return b.taskQueue +} + +func (b *SBaremetalInstance) GetTask() tasks.ITask { + return b.taskQueue.GetTask() +} + +func (b *SBaremetalInstance) SetTask(task tasks.ITask) { + b.taskQueue.AppendTask(task) + if reflect.DeepEqual(task, b.taskQueue.GetTask()) { + log.Infof("Set task equal, ExecuteTask %s", task.GetName()) + tasks.ExecuteTask(task, nil) + } +} + +func (b *SBaremetalInstance) InitAdminNetif( + cliMac net.HardwareAddr, + netConf *types.SNetworkConfig, + nicType string, + netType string, +) error { + // start prepare task + // sync status to PREPARE + if nicType == types.NIC_TYPE_ADMIN && + utils.IsInStringArray(b.GetStatus(), + []string{baremetalstatus.INIT, + baremetalstatus.PREPARE, + baremetalstatus.PREPARE_FAIL, + baremetalstatus.UNKNOWN}) && + b.GetTask() == nil && b.GetServer() == nil { + b.SetTask(tasks.NewBaremetalServerPrepareTask(b)) + b.SyncStatus(baremetalstatus.PREPARE, "") + } + + nic := b.GetNicByMac(cliMac) + if nic == nil || nic.WireId == "" { + _, err := b.attachWire(cliMac, netConf.WireId, nicType) + if err != nil { + return err + } + return b.postAttachWire(cliMac, nicType, netType) + } else if nic.IpAddr == "" { + return b.postAttachWire(cliMac, nicType, netType) + } + return nil +} + +func (b *SBaremetalInstance) RegisterNetif( + cliMac net.HardwareAddr, + netConf *types.SNetworkConfig, +) error { + nic := b.GetNicByMac(cliMac) + if nic == nil || nic.WireId == "" || nic.WireId != netConf.WireId { + desc, err := b.attachWire(cliMac, netConf.WireId, nic.Type) + if err != nil { + return err + } + return b.SaveDesc(desc) + } + return nil +} + +func (b *SBaremetalInstance) attachWire(mac net.HardwareAddr, wireId string, nicType string) (jsonutils.JSONObject, error) { + session := b.manager.GetClientSession() + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(mac.String()), "mac") + if nicType != "" { + params.Add(jsonutils.NewString(nicType), "nic_type") + } + params.Add(jsonutils.NewString(wireId), "wire") + params.Add(jsonutils.JSONTrue, "link_up") + return modules.Hosts.PerformAction(session, b.GetId(), "add-netif", params) +} + +func (b *SBaremetalInstance) postAttachWire(mac net.HardwareAddr, nicType string, netType string) error { + ipAddr := "" + if nicType == types.NIC_TYPE_IPMI { + oldIPMIConf := b.GetRawIPMIConfig() + if oldIPMIConf != nil && oldIPMIConf.IpAddr != "" { + ipAddr = oldIPMIConf.IpAddr + } + } + desc, err := b.enableWire(mac, ipAddr, nicType, netType) + if err != nil { + return err + } + return b.SaveDesc(desc) +} + +func (b *SBaremetalInstance) enableWire(mac net.HardwareAddr, ipAddr string, nicType string, netType string) (jsonutils.JSONObject, error) { + session := b.manager.GetClientSession() + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(mac.String()), "mac") + if nicType != "" { + params.Add(jsonutils.NewString(nicType), "nic_type") + } + if ipAddr != "" { + params.Add(jsonutils.NewString(ipAddr), "ip_addr") + } + if nicType == types.NIC_TYPE_IPMI { + params.Add(jsonutils.NewString("stepup"), "alloc_dir") // alloc bottom up + } + if len(netType) > 0 { + params.Add(jsonutils.NewString(netType), "net_type") + } + log.Errorf("enable net if params: %s", params.String()) + return modules.Hosts.PerformAction(session, b.GetId(), "enable-netif", params) +} + +func (b *SBaremetalInstance) GetIPMIConfig() *types.SIPMIInfo { + conf := b.GetRawIPMIConfig() + if conf == nil || conf.Password == "" { + return nil + } + if conf.Username == "" { + sysInfo := types.SIPMISystemInfo{} + err := b.desc.Unmarshal(&sysInfo, "sys_info") + if err != nil { + log.Errorf("Unmarshal get sys_info error: %v", err) + } + conf.Username = profiles.GetRootName(&sysInfo) + } + if conf.IpAddr == "" { + nicIPAddr := b.GetIPMINicIPAddr() + if nicIPAddr != "" { + conf.IpAddr = nicIPAddr + } + } + conf.Password = utils.Unquote(conf.Password) // XXX: remove quotes!!! + if conf.IpAddr == "" { + return nil + } + return conf +} + +func (b *SBaremetalInstance) GetRawIPMIConfig() *types.SIPMIInfo { + ipmiInfo := types.SIPMIInfo{} + err := b.desc.Unmarshal(&ipmiInfo, "ipmi_info") + if err != nil { + log.Errorf("Unmarshal IPMIInfo error: %v", err) + return nil + } + if ipmiInfo.Password != "" { + ipmiInfo.Password, err = utils.DescryptAESBase64(b.GetId(), ipmiInfo.Password) + if err != nil { + log.Errorf("DescryptAESBase64 IPMI password error: %v", err) + return nil + } + } + return &ipmiInfo +} + +func (b *SBaremetalInstance) GetServer() baremetaltypes.IBaremetalServer { + b.serverLock.Lock() + defer b.serverLock.Unlock() + if !b.desc.Contains("server_id") && b.server != nil { + log.Warningf("baremetal %s server_id not present, remove server %q", b.GetName(), b.server.GetName()) + b.RemoveServer() + return nil + } + return b.server +} + +func (b *SBaremetalInstance) GetServerId() string { + srv := b.GetServer() + if srv == nil { + return "" + } + return srv.GetId() +} + +func (b *SBaremetalInstance) GetServerName() string { + srv := b.GetServer() + if srv == nil { + return "" + } + return srv.GetName() +} + +func (b *SBaremetalInstance) RemoveServer() { + b.serverLock.Lock() + defer b.serverLock.Unlock() + b.removeServer() +} + +func (b *SBaremetalInstance) removeServer() { + if b.server != nil { + b.server.RemoveDesc() + b.server = nil + } +} + +func (b *SBaremetalInstance) SetExistingIPMIIPAddr(ipAddr string) { + info, _ := b.desc.Get("ipmi_info") + if info == nil { + info = jsonutils.NewDict() + } + oIPAddr, _ := info.GetString("ip_addr") + if oIPAddr == "" { + info.(*jsonutils.JSONDict).Add(jsonutils.NewString(ipAddr), "ip_addr") + } + b.desc.Set("ipmi_info", info) +} + +func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI { + conf := b.GetIPMIConfig() + if conf == nil { + return nil + } + return ipmitool.NewLanPlusIPMI(conf.IpAddr, conf.Username, conf.Password) +} + +func (b *SBaremetalInstance) GetIPMILanChannel() int { + conf := b.GetIPMIConfig() + if conf == nil { + return 0 + } + return conf.LanChannel +} + +func (b *SBaremetalInstance) DoPXEBoot() error { + log.Infof("Do PXE Boot ........., wait") + b.ClearSSHConfig() + ipmiCli := b.GetIPMITool() + if ipmiCli != nil { + return ipmitool.DoRebootToPXE(ipmiCli) + } + return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) +} + +func (b *SBaremetalInstance) DoDiskBoot() error { + log.Infof("Do DISK Boot ........., wait") + b.ClearSSHConfig() + ipmiCli := b.GetIPMITool() + if ipmiCli != nil { + return ipmitool.DoRebootToDisk(ipmiCli) + } + return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) +} + +func (b *SBaremetalInstance) GetPowerStatus() (string, error) { + ipmiCli := b.GetIPMITool() + if ipmiCli == nil { + return "", fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) + } + return ipmitool.GetChassisPowerStatus(ipmiCli) +} + +func (b *SBaremetalInstance) DoPowerShutdown(soft bool) error { + b.ClearSSHConfig() + ipmiCli := b.GetIPMITool() + if ipmiCli != nil { + if soft { + return ipmitool.DoSoftShutdown(ipmiCli) + } + return ipmitool.DoHardShutdown(ipmiCli) + } + return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) +} + +func (b *SBaremetalInstance) GetStorageDriver() string { + driver, _ := b.desc.GetString("storage_driver") + return driver +} + +func (b *SBaremetalInstance) GetZoneId() string { + return b.manager.GetZoneId() +} + +func (b *SBaremetalInstance) DelayedRemove(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) { + b.remove() + return nil, nil +} + +func (b *SBaremetalInstance) remove() { + b.manager.CleanBaremetal(b.GetId()) + b.manager = nil + b.desc = nil +} + +func (b *SBaremetalInstance) StartNewTask(factory tasks.TaskFactory, taskId string, data jsonutils.JSONObject) { + go func() { + task, err := factory(b, taskId, data) + if err != nil { + tasks.SetTaskFail(task, err) + return + } + b.SetTask(task) + }() +} + +func (b *SBaremetalInstance) StartBaremetalMaintenanceTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + if jsonutils.QueryBoolean(data, "force_reboot", false) { + b.ClearSSHConfig() + } + if jsonutils.QueryBoolean(data, "guest_running", false) { + data.(*jsonutils.JSONDict).Set("soft_reboot", jsonutils.JSONTrue) + } + b.StartNewTask(tasks.NewBaremetalMaintenanceTask, taskId, data) +} + +func (b *SBaremetalInstance) StartBaremetalUnmaintenanceTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + b.StartNewTask(tasks.NewBaremetalUnmaintenanceTask, taskId, data) +} + +func (b *SBaremetalInstance) StartBaremetalReprepareTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + b.StartNewTask(tasks.NewBaremetalReprepareTask, taskId, data) +} + +func (b *SBaremetalInstance) StartBaremetalResetBMCTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + b.StartNewTask(tasks.NewBaremetalResetBMCTask, taskId, data) + return nil +} + +func (b *SBaremetalInstance) DelayedServerReset(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) { + err := b.DoDiskBoot() + return nil, err +} + +func (b *SBaremetalInstance) StartServerCreateTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + b.serverLock.Lock() + defer b.serverLock.Unlock() + if b.server != nil { + return fmt.Errorf("Baremetal %s already have server %s", b.GetName(), b.server.GetName()) + } + descData, err := data.Get("desc") + if err != nil { + return fmt.Errorf("Create data not found server desc: %v", err) + } + server, err := newBaremetalServer(b, descData.(*jsonutils.JSONDict)) + if err != nil { + return fmt.Errorf("New server error: %v", err) + } + b.server = server + b.desc.Set("server_id", jsonutils.NewString(b.server.GetId())) + if err := b.AutoSaveDesc(); err != nil { + return err + } + b.StartNewTask(tasks.NewBaremetalServerCreateTask, taskId, data) + return nil +} + +func (b *SBaremetalInstance) StartServerDeployTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + desc, err := data.Get("desc") + if err != nil { + return fmt.Errorf("Not found desc in data") + } + if err := b.GetServer().SaveDesc(desc); err != nil { + return fmt.Errorf("Save server desc: %v", err) + } + b.StartNewTask(tasks.NewBaremetalServerDeployTask, taskId, data) + return nil +} + +func (b *SBaremetalInstance) StartServerRebuildTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + desc, err := data.Get("desc") + if err != nil { + return fmt.Errorf("Not found desc in data") + } + if err := b.GetServer().SaveDesc(desc); err != nil { + return fmt.Errorf("Save server desc: %v", err) + } + b.StartNewTask(tasks.NewBaremetalServerRebuildTask, taskId, data) + return nil +} + +func (b *SBaremetalInstance) StartServerStartTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + b.StartNewTask(tasks.NewBaremetalServerStartTask, taskId, data) + return nil +} + +func (b *SBaremetalInstance) StartServerStopTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + b.StartNewTask(tasks.NewBaremetalServerStopTask, taskId, data) + return nil +} + +func (b *SBaremetalInstance) StartServerDestroyTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + b.StartNewTask(tasks.NewBaremetalServerDestroyTask, taskId, data) +} + +func (b *SBaremetalInstance) DelayedSyncIPMIInfo(data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + ipmiCli := b.GetIPMITool() + lanChannel := b.GetIPMILanChannel() + sysInfo, err := ipmitool.GetSysInfo(ipmiCli) + if err != nil { + return nil, err + } + if lanChannel <= 0 { + lanChannel = ipmitool.GetDefaultLanChannel(sysInfo) + } + retObj := make(map[string]string) + if ipAddr, _ := data.GetString("ip_addr"); ipAddr != "" { + err = ipmitool.SetLanStaticIP(ipmiCli, lanChannel, ipAddr) + if err != nil { + return nil, err + } + // TODO: netutils.wait_ip_alive(ipAddr, 120) + retObj["ipmi_ip_addr"] = ipAddr + } + if passwd, _ := data.GetString("password"); passwd != "" { + err = ipmitool.SetLanPasswd(ipmiCli, ipmitool.GetRootId(sysInfo), passwd) + if err != nil { + return nil, err + } + retObj["ipmi_password"] = passwd + } + return jsonutils.Marshal(retObj), nil +} + +func (b *SBaremetalInstance) DelayedSyncDesc(data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + err := b.SaveDesc(data) + return nil, err +} + +func (b *SBaremetalInstance) DelayedServerStatus(data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + ps, err := b.GetPowerStatus() + if err != nil { + return nil, err + } + status := PowerStatusToServerStatus(b, ps) + resp := jsonutils.NewDict() + resp.Add(jsonutils.NewString(status), "status") + return resp, err +} + +type SBaremetalServer struct { + baremetal *SBaremetalInstance + desc *jsonutils.JSONDict +} + +func newBaremetalServer(baremetal *SBaremetalInstance, desc *jsonutils.JSONDict) (*SBaremetalServer, error) { + server := &SBaremetalServer{ + baremetal: baremetal, + desc: desc, + } + err := server.SaveDesc(desc) + return server, err +} + +func (server *SBaremetalServer) GetId() string { + id, err := server.desc.GetString("uuid") + if err != nil { + log.Fatalf("Get id from desc error: %v", err) + } + return id +} + +func (server *SBaremetalServer) GetName() string { + id, err := server.desc.GetString("name") + if err != nil { + log.Fatalf("Get name from desc error: %v", err) + } + return id +} + +func (server *SBaremetalServer) SaveDesc(desc jsonutils.JSONObject) error { + if desc != nil { + server.desc = desc.(*jsonutils.JSONDict) + } + return ioutil.WriteFile(server.baremetal.GetServerDescFilePath(), []byte(server.desc.String()), 0644) +} + +func (s *SBaremetalServer) RemoveDesc() { + os.Remove(s.baremetal.GetServerDescFilePath()) + s.desc = nil + s.baremetal = nil +} + +func (s *SBaremetalServer) GetRootTemplateId() string { + rootDisk, err := s.desc.GetAt(0, "disks") + if err != nil { + log.Errorf("Can't found root disk") + return "" + } + id, _ := rootDisk.GetString("template_id") + return id +} + +func (s *SBaremetalServer) GetDiskConfig() ([]*baremetal.BaremetalDiskConfig, error) { + layouts := make([]baremetal.Layout, 0) + err := s.desc.Unmarshal(&layouts, "disk_config") + if err != nil { + return nil, err + } + return baremetal.GetLayoutRaidConfig(layouts), nil +} + +func (s *SBaremetalServer) DoDiskConfig(term *ssh.Client) error { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, true) + if err != nil { + return err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return fmt.Errorf("CalculateLayout: %v", err) + } + diskConfs := baremetal.GroupLayoutResultsByDriverAdapter(layouts) + for _, dConf := range diskConfs { + driver := dConf.Driver + raidDrv := raiddrivers.GetDriver(driver, term) + if raidDrv != nil { + if err := raidDrv.ParsePhyDevs(); err != nil { + return fmt.Errorf("RaidDriver %s parse physical devices: %v", raidDrv.GetName(), err) + } + raidDrv.CleanRaid() + } + } + + for _, dConf := range diskConfs { + driver := dConf.Driver + adapter := dConf.Adapter + raidDrv := raiddrivers.GetDriver(driver, term) + if raidDrv != nil { + if err := raidDrv.ParsePhyDevs(); err != nil { + return fmt.Errorf("RaidDriver %s parse physical devices: %v", raidDrv.GetName(), err) + } + if err := raiddrivers.BuildRaid(raidDrv, dConf.Configs, adapter); err != nil { + return fmt.Errorf("Build %s raid failed: %v", raidDrv.GetName(), err) + } + time.Sleep(10 * time.Second) // wait 10 seconds for raid status OK + } + } + + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + err = tool.RetrieveDiskInfo() + if err != nil { + return err + } + maxTries := 60 + for tried := 0; !tool.IsAllDisksReady() && tried < maxTries; tried++ { + time.Sleep(5 * time.Second) + tool.RetrieveDiskInfo() + } + + if !tool.IsAllDisksReady() { + return fmt.Errorf("Raid disks are not ready???") + } + + return nil +} + +func (s *SBaremetalServer) DoDiskUnconfig(term *ssh.Client) error { + // tear down raid + driver := s.baremetal.GetStorageDriver() + raidDrv := raiddrivers.GetDriver(driver, term) + if raidDrv != nil { + if err := raidDrv.ParsePhyDevs(); err != nil { + return err + } + raidDrv.CleanRaid() + } + return nil +} + +func (s *SBaremetalServer) DoEraseDisk(term *ssh.Client) error { + cmd := "/lib/mos/partdestroy.sh" + _, err := term.Run(cmd) + return err +} + +func (s *SBaremetalServer) doCreateRoot(term *ssh.Client, devName string) error { + session := s.baremetal.GetClientSession() + token := session.GetToken().GetTokenString() + url, err := session.GetServiceURL("image", "internalURL") + if err != nil { + return err + } + imageId := s.GetRootTemplateId() + cmd := fmt.Sprintf("/lib/mos/rootcreate.sh %s %s %s %s", token, url, imageId, devName) + log.Infof("rootcreate cmd: %q", cmd) + if _, err := term.Run(cmd); err != nil { + return fmt.Errorf("Root create fail: %v", err) + } + return nil +} + +func (s *SBaremetalServer) DoPartitionDisk(term *ssh.Client) ([]*disktool.Partition, error) { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, false) + if err != nil { + return nil, err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return nil, err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return nil, err + } + + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + err = tool.RetrieveDiskInfo() + if err != nil { + return nil, err + } + + disks, _ := s.desc.GetArray("disks") + if len(disks) == 0 { + return nil, fmt.Errorf("Empty disks in desc") + } + + rootDisk := disks[0] + rootSize, _ := rootDisk.Int("size") + err = s.doCreateRoot(term, tool.GetRootDisk().GetDevName()) + if err != nil { + return nil, fmt.Errorf("Failed to create root: %v", err) + } + + tool.RetrievePartitionInfo() + parts := tool.GetPartitions() + if len(parts) == 0 { + return nil, fmt.Errorf("Root disk create failed, no partitions") + } + log.Infof("Resize root to %d MB", rootSize) + if err := tool.ResizePartition(0, rootSize); err != nil { + return nil, fmt.Errorf("Fail to resize root to %d, err: %v", rootSize, err) + } + if len(disks) > 1 { + for _, disk := range disks[1:] { + sz, err := disk.Int("size") + if err != nil { + sz = -1 + } + fs, _ := disk.GetString("fs") + uuid, _ := disk.GetString("disk_id") + driver, _ := disk.GetString("driver") + log.Infof("Create partition %d %s", sz, fs) + if err := tool.CreatePartition(-1, sz, fs, true, driver, uuid); err != nil { + return nil, fmt.Errorf("Fail to create disk %s: %v", disk.String(), err) + } + } + } + log.Infof("Finish create partitions") + + return tool.GetPartitions(), nil +} + +func (s *SBaremetalServer) DoRebuildRootDisk(term *ssh.Client) ([]*disktool.Partition, error) { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, false) + if err != nil { + return nil, err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return nil, err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return nil, err + } + + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + err = tool.RetrieveDiskInfo() + if err != nil { + return nil, err + } + + disks, _ := s.desc.GetArray("disks") + if len(disks) == 0 { + return nil, fmt.Errorf("Empty disks in desc") + } + + rootDisk := disks[0] + rootSize, _ := rootDisk.Int("size") + err = s.doCreateRoot(term, tool.GetRootDisk().GetDevName()) + if err != nil { + return nil, fmt.Errorf("Failed to create root: %v", err) + } + + tool.RetrievePartitionInfo() + + log.Infof("Resize root to %d MB", rootSize) + if err := tool.ResizePartition(0, rootSize); err != nil { + return nil, fmt.Errorf("Fail to resize root to %d, err: %v", rootSize, err) + } + if len(disks) > 1 { + for _, disk := range disks[1:] { + sz, err := disk.Int("size") + if err != nil { + sz = -1 + } + fs, _ := disk.GetString("fs") + uuid, _ := disk.GetString("disk_id") + driver, _ := disk.GetString("driver") + log.Infof("Create partition %d %s", sz, fs) + if err := tool.CreatePartition(0, sz, fs, false, driver, uuid); err != nil { + log.Errorf("Rebuild root create (%s, %d, %s, %s) partition error: %v", uuid, sz, fs, driver, err) + break + } + } + } + log.Infof("Finish create partitions") + + return tool.GetPartitions(), nil +} + +func (s *SBaremetalServer) SyncPartitionSize(term *ssh.Client, parts []*disktool.Partition) ([]jsonutils.JSONObject, error) { + disks, _ := s.desc.GetArray("disks") + rootPartsCnt := len(parts) - len(disks) + 1 + rootParts := parts[0:rootPartsCnt] + dataParts := parts[rootPartsCnt:] + idx := 0 + size := (rootParts[len(rootParts)-1].GetEnd() + 1) * 512 / 1024 / 1024 + disks[idx].(*jsonutils.JSONDict).Set("size", jsonutils.NewInt(int64(size))) + idx += 1 + for _, p := range dataParts { + sizeMB, err := p.GetSizeMB() + if err != nil { + return nil, err + } + disks[idx].(*jsonutils.JSONDict).Set("size", jsonutils.NewInt(int64(sizeMB))) + disks[idx].(*jsonutils.JSONDict).Set("dev", jsonutils.NewString(p.GetDev())) + idx++ + } + return disks, nil +} + +func (s *SBaremetalServer) DoDeploy(term *ssh.Client, data jsonutils.JSONObject, isInit bool) (jsonutils.JSONObject, error) { + publicKey := sshkeys.GetKeys(data) + deploys, _ := data.GetArray("deploys") + password, _ := data.GetString("password") + resetPassword := jsonutils.QueryBoolean(data, "reset_password", false) + if resetPassword && len(password) == 0 { + password = seclib.RandomPassword(12) + } + deployInfo := guestfs.NewDeployInfo(publicKey, deploys, password, isInit, true, o.Options.LinuxDefaultRootUser, o.Options.WindowsDefaultAdminUser) + return s.deployFs(term, deployInfo) +} + +func (s *SBaremetalServer) deployFs(term *ssh.Client, deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, false) + if err != nil { + return nil, err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return nil, err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return nil, err + } + rootDev, rootfs, err := sshpart.MountSSHRootfs(term, layouts) + if err != nil { + return nil, fmt.Errorf("Find rootfs error: %s", err) + } + defer rootDev.Umount() + if strings.ToLower(rootfs.GetOs()) == "windows" { + return nil, fmt.Errorf("Unsupported OS: %s", rootfs.GetOs()) + } + return guestfs.DeployGuestFs(rootfs, s.desc, deployInfo) +} + +func (s *SBaremetalServer) GetNics() []types.SServerNic { + nics := []types.SServerNic{} + err := s.desc.Unmarshal(&nics, "nics") + if err != nil { + log.Errorf("Unmarshal desc to get server nics error: %v", err) + return nil + } + return nics +} + +func (s *SBaremetalServer) GetNicByMac(mac net.HardwareAddr) *types.SNic { + for _, n := range s.GetNics() { + if n.GetMac().String() == mac.String() { + nic := n.ToNic() + return &nic + } + } + return nil +} diff --git a/pkg/baremetal/nic.go b/pkg/baremetal/nic.go new file mode 100644 index 0000000000..7206d89827 --- /dev/null +++ b/pkg/baremetal/nic.go @@ -0,0 +1,84 @@ +package baremetal + +import ( + "fmt" + "net" + "os" + "path/filepath" + "time" + + "yunion.io/x/pkg/util/netutils" + + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/dhcp" +) + +func GetNicDHCPConfig( + n *types.SNic, + serverIP string, + hostName string, + isPxe bool, + arch uint16, +) (*dhcp.ResponseConfig, error) { + if n == nil { + return nil, fmt.Errorf("Nic is nil") + } + if n.IpAddr == "" { + return nil, fmt.Errorf("Nic no ip address") + } + ipAddr, err := netutils.NewIPV4Addr(n.IpAddr) + if err != nil { + return nil, fmt.Errorf("Parse IP address error: %q", n.IpAddr) + } + + subnetMask := net.ParseIP(netutils.Masklen2Mask(n.MaskLen).String()) + + routes := make([][]string, 0) + for _, route := range n.Routes { + routes = append(routes, []string{route[0], route[1]}) + } + + conf := &dhcp.ResponseConfig{ + ServerIP: net.ParseIP(serverIP), + ClientIP: net.ParseIP(ipAddr.String()), + Gateway: net.ParseIP(n.Gateway), + SubnetMask: subnetMask, + BroadcastAddr: net.ParseIP(ipAddr.BroadcastAddr(n.MaskLen).String()), + DNSServer: net.ParseIP(n.Dns), + Domain: n.Domain, + OsName: "Linux", + Hostname: hostName, + Routes: routes, + LeaseTime: time.Duration(o.Options.DhcpLeaseTime) * time.Second, + RenewalTime: time.Duration(o.Options.DhcpRenewalTime) * time.Second, + } + + if isPxe { + conf.BootServer = serverIP + switch arch { + case 7, 9: + conf.BootFile = "bootx64.efi" + case 6: + conf.BootFile = "bootia32.efi" + default: + conf.BootFile = "pxelinux.0" + } + pxePath := filepath.Join(o.Options.TftpRoot, conf.BootFile) + if f, err := os.Open(pxePath); err != nil { + return nil, err + } else { + if info, err := f.Stat(); err != nil { + return nil, err + } else { + pxeSize := info.Size() + pxeBlk := pxeSize / 512 + if pxeSize > pxeBlk*512 { + pxeBlk += 1 + } + conf.BootBlock = uint16(pxeBlk) + } + } + } + return conf, nil +} diff --git a/pkg/baremetal/options/doc.go b/pkg/baremetal/options/doc.go new file mode 100644 index 0000000000..3db6801a41 --- /dev/null +++ b/pkg/baremetal/options/doc.go @@ -0,0 +1 @@ +package options // import "yunion.io/x/onecloud/pkg/baremetal/options" diff --git a/pkg/baremetal/options/options.go b/pkg/baremetal/options/options.go new file mode 100644 index 0000000000..5fa018a65c --- /dev/null +++ b/pkg/baremetal/options/options.go @@ -0,0 +1,35 @@ +package options + +import ( + "yunion.io/x/onecloud/pkg/cloudcommon" +) + +type BaremetalOptions struct { + cloudcommon.CommonOptions + + ListenInterface string `help:"Master net interface of baremetal server" default:"br0"` + AccessAddress string `help:"Management IP address of baremetal server, only need to use when multiple address bind to ListenInterface"` + ListenAddress string `help:"PXE serve IP address to select when multiple address bind to ListenInterface" default:"0.0.0.0"` + TftpRoot string `help:"tftp root directory"` + AutoRegisterBaremetal bool `default:"true" help:"Automatically create a baremetal instance"` + BaremetalsPath string `default:"/opt/cloud/workspace/baremetals" help:"Path for baremetals configuration files"` + LinuxDefaultRootUser bool `default:"false" help:"Default account for linux system is root"` + IpmiLanPortShared bool `default:"false" help:"IPMI Lan port shared or dedicated"` + Zone string `help:"Zone where the agent locates"` + DhcpLeaseTime int `default:"100663296" help:"DHCP lease time in seconds"` // 0x6000000 + DhcpRenewalTime int `default:"67108864" help:"DHCP renewal time in seconds"` // 0x4000000 + EnableGeneralGuestDhcp bool `default:"false" help:"Enable DHCP service for general guest, e.g. those on VMware ESXi or Xen"` + ForceDhcpProbeIpmi bool `default:"false" help:"Force DHCP probe IPMI interface network connection"` + TftpMaxTimeoutRetries int `default:"20" help:"Maximal tftp timeout retries, default is 20"` + LengthyWorkerCount int `default:"8" help:"Parallel worker count for lengthy tasks"` + ShortWorkerCount int `default:"8" help:"Parallel worker count for short-lived tasks"` + + DefaultIpmiPassword string `help:"Default IPMI passowrd"` + DefaultStrongIpmiPassword string `help:"Default strong IPMI passowrd"` + + WindowsDefaultAdminUser bool `default:"true" help:"Default account for Windows system is Administrator"` +} + +var ( + Options BaremetalOptions +) diff --git a/pkg/baremetal/profiles/doc.go b/pkg/baremetal/profiles/doc.go new file mode 100644 index 0000000000..0751ba7b63 --- /dev/null +++ b/pkg/baremetal/profiles/doc.go @@ -0,0 +1 @@ +package profiles // import "yunion.io/x/onecloud/pkg/baremetal/profiles" diff --git a/pkg/baremetal/profiles/profiles.go b/pkg/baremetal/profiles/profiles.go new file mode 100644 index 0000000000..dd5ef5f4f2 --- /dev/null +++ b/pkg/baremetal/profiles/profiles.go @@ -0,0 +1,108 @@ +package profiles + +import ( + "strings" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" +) + +type IPMIProfile struct { + LanChannel []int + RootName string + RootId int + StrongPass bool +} + +func DefaultProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{1}, + RootName: "root", + RootId: 2, + } +} + +func InspurProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{8, 1}, + RootName: "admin", + RootId: 2, + } +} + +func LenovoProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{8}, + RootName: "root", + RootId: 2, + } +} + +func HpProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{2}, + RootName: "root", + RootId: 1, + } +} + +func HuaweiProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{1}, + RootName: "root", + RootId: 2, + StrongPass: true, + } +} + +func FoxconnProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{1}, + RootName: "root", + RootId: 2, + StrongPass: true, + } +} + +func QemuProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{8, 1}, + RootName: "root", + RootId: 2, + StrongPass: true, + } +} + +var ( + PROFILES map[string]IPMIProfile = map[string]IPMIProfile{ + "inspur": InspurProfile(), + "lenovo": LenovoProfile(), + "hp": HpProfile(), + "huawei": HuaweiProfile(), + "foxconn": FoxconnProfile(), + "qemu": QemuProfile(), + } +) + +func GetProfile(sysinfo *types.SIPMISystemInfo) IPMIProfile { + profile, ok := PROFILES[strings.ToLower(sysinfo.Manufacture)] + if ok { + return profile + } + return DefaultProfile() +} + +func GetLanChannel(sysinfo *types.SIPMISystemInfo) []int { + return GetProfile(sysinfo).LanChannel +} + +func GetRootId(sysinfo *types.SIPMISystemInfo) int { + return GetProfile(sysinfo).RootId +} + +func GetRootName(sysinfo *types.SIPMISystemInfo) string { + return GetProfile(sysinfo).RootName +} + +func IsStrongPass(sysinfo *types.SIPMISystemInfo) bool { + return GetProfile(sysinfo).StrongPass +} diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go new file mode 100644 index 0000000000..c4d0283324 --- /dev/null +++ b/pkg/baremetal/pxe/dhcp.go @@ -0,0 +1,361 @@ +package pxe + +import ( + "errors" + "fmt" + "net" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/dhcp" +) + +func (s *Server) serveDHCP(srv *dhcp.DHCPServer, handler dhcp.DHCPHandler) error { + return srv.ListenAndServe(handler) +} + +type NetworkInterfaceIdent struct { + Type uint16 + Major uint16 + Minior uint16 +} + +type DHCPHandler struct { + ClientMac net.HardwareAddr // client nic mac + ClientAddr net.IP // IP address from DHCP client + RelayAddr net.IP // IP address of DHCP relay agent + Options dhcp.Options // dhcp packet options + VendorClassId string + ClientArch uint16 + NetworkInterfaceIdent NetworkInterfaceIdent + ClientGuid string + packet dhcp.Packet + + // baremetal manager + baremetalManager IBaremetalManager + // baremetal instance + baremetalInstance IBaremetalInstance + // cloud network config + netConfig *types.SNetworkConfig +} + +func (h *DHCPHandler) ServeDHCP(pkt dhcp.Packet, _ *net.UDPAddr, _ *net.Interface) (dhcp.Packet, error) { + //log.V(4).Debugf("[DHCP] request: %s", pkt.DebugString()) + err := h.parsePacket(pkt) + if err != nil { + log.Errorf("[DHCP] parse packet error: %v", err) + } + log.V(4).Debugf("[DHCP] parse packet end: %#v", h) + + if h.RelayAddr.String() == "0.0.0.0" { + return nil, fmt.Errorf("Request not from a DHCP relay, ignore mac: %s", h.ClientMac) + } + conf, err := h.fetchConfig() + if err != nil { + return nil, err + } + if conf == nil { + return nil, fmt.Errorf("Empty packet config") + } + return dhcp.MakeReplyPacket(pkt, conf) +} + +func (h *DHCPHandler) parsePacket(pkt dhcp.Packet) error { + h.packet = pkt + h.ClientAddr = pkt.CIAddr() + h.ClientMac = pkt.CHAddr() + h.RelayAddr = pkt.RelayAddr() + h.Options = pkt.ParseOptions() + + var ( + vendorClsId string + cliArch uint16 + err error + netIfIdent NetworkInterfaceIdent + cliGuid string + ) + + for optCode, data := range h.Options { + switch optCode { + case dhcp.OptionVendorClassIdentifier: + vendorClsId, err = h.Options.String(optCode) + case dhcp.OptionClientArchitecture: + cliArch, err = h.Options.Uint16(optCode) + case dhcp.OptionClientNetworkInterfaceIdentifier: + netIfIdentBs, err := h.Options.Bytes(optCode) + if err != nil { + break + } + netIfIdent = NetworkInterfaceIdent{ + Type: uint16(netIfIdentBs[0]), + Major: uint16(netIfIdentBs[1]), + Minior: uint16(netIfIdentBs[2]), + } + log.Debugf("[DHCP] get network iface identifier: %#v", netIfIdent) + case dhcp.OptionClientMachineIdentifier: + switch len(data) { + case 0: + // A missing GUID is invalid according to the spec, however + // there are PXE ROMs in the wild that omit the GUID and still + // expect to boot. + case 17: + if data[0] != 0 { + err = errors.New("malformed client GUID (option 97), leading byte must be zero") + } + default: + err = errors.New("malformed client GUID (option 97), wrong size") + } + cliGuid, err = h.Options.String(optCode) + } + if err != nil { + log.Errorf("[DHCP] parse vendor option %d error: %v", optCode, err) + } + } + h.VendorClassId = vendorClsId + h.ClientArch = cliArch + h.NetworkInterfaceIdent = netIfIdent + h.ClientGuid = cliGuid + return err +} + +func (h *DHCPHandler) fetchConfig() (*dhcp.ResponseConfig, error) { + // 1. find_network_conf + netConf, err := h.findNetworkConf(false) + if err != nil { + return nil, err + } + h.netConfig = netConf + + // TODO: set cache for netConf + // + if h.isPXERequest() { + // handle PXE DHCP request + log.Infof("DHCP relay from %s(%s) for %s, find matched networks: %#v", h.RelayAddr, h.ClientAddr, h.ClientMac, netConf) + bmDesc, err := h.createOrUpdateBaremetal() + if err != nil { + return nil, err + } + err = h.doInitBaremetalAdminNetif(bmDesc) + if err != nil { + return nil, err + } + if h.baremetalInstance.NeedPXEBoot() { + return h.baremetalInstance.GetPXEDHCPConfig(h.ClientArch) + } + // ignore + log.Warningf("No need to pxeboot, ignore the request ...(mac:%s guid:%s)", h.ClientMac, h.ClientGuid) + return nil, nil + } else { + // handle normal DHCP request + bmInstance := h.baremetalManager.GetBaremetalByMac(h.ClientMac) + if bmInstance == nil { + // options.EnableGeneralGuestDhcp + // cloud be an instance not served by a host-server + // from guestdhcp import GuestDHCPHelperTask + // task = GuestDHCPHelperTask(self) + // task.start() + return nil, nil + } + h.baremetalInstance = bmInstance + ipmiNic := h.baremetalInstance.GetIPMINic(h.ClientMac) + if ipmiNic != nil && ipmiNic.Mac == h.ClientMac.String() { + err = h.baremetalInstance.InitAdminNetif(h.ClientMac, h.netConfig, types.NIC_TYPE_IPMI, models.NETWORK_TYPE_IPMI) + if err != nil { + return nil, err + } + } else { + err = h.baremetalInstance.RegisterNetif(h.ClientMac, h.netConfig) + if err != nil { + log.Errorf("RegisterNetif error: %v", err) + return nil, err + } + } + return h.baremetalInstance.GetDHCPConfig(h.ClientMac) + } +} + +func (h *DHCPHandler) findNetworkConf(filterUseIp bool) (*types.SNetworkConfig, error) { + params := jsonutils.NewDict() + if filterUseIp { + params.Add(jsonutils.NewString(h.RelayAddr.String()), "ip") + } else { + params.Add(jsonutils.NewString( + fmt.Sprintf("guest_gateway.equals(%s)", h.RelayAddr)), + "filter.0") + params.Add(jsonutils.NewString( + fmt.Sprintf("guest_dhcp.equals(%s)", h.RelayAddr)), + "filter.1") + params.Add(jsonutils.JSONTrue, "filter_any") + } + params.Add(jsonutils.JSONTrue, "is_private") + session := h.baremetalManager.GetClientSession() + ret, err := modules.Networks.List(session, params) + if err != nil { + return nil, err + } + if len(ret.Data) == 0 { + if !filterUseIp { + // use ip filter try again + return h.findNetworkConf(true) + } + return nil, fmt.Errorf("DHCP relay from %s(%s) for %s, find no match network", h.RelayAddr, h.ClientAddr, h.ClientMac) + } + + network := types.SNetworkConfig{} + err = ret.Data[0].Unmarshal(&network) + return &network, err +} + +// createOrUpdateBaremetal create or update baremetal by client MAC +func (h *DHCPHandler) createOrUpdateBaremetal() (jsonutils.JSONObject, error) { + session := h.baremetalManager.GetClientSession() + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(h.ClientMac.String()), "any_mac") + ret, err := modules.Hosts.List(session, params) + if err != nil { + return nil, err + } + switch len(ret.Data) { + case 0: + // found new baremetal, create it if auto register + if o.Options.AutoRegisterBaremetal { + return h.createBaremetal() + } + case 1: + // already exists, do update + bmId, err := ret.Data[0].GetString("id") + if err != nil { + return nil, err + } + return h.updateBaremetal(bmId) + } + return nil, fmt.Errorf("Found %d records match %s", len(ret.Data), h.ClientMac) +} + +func (h *DHCPHandler) createBaremetal() (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + mac := h.ClientMac.String() + zoneId := h.baremetalManager.GetZoneId() + name := fmt.Sprintf("BM%s", strings.Replace(mac, ":", "", -1)) + params.Add(jsonutils.NewString(name), "name") + params.Add(jsonutils.NewString(mac), "access_mac") + params.Add(jsonutils.NewString("baremetal"), "host_type") + params.Add(jsonutils.JSONTrue, "is_baremetal") + params.Add(jsonutils.NewString(zoneId), "zone_id") + session := h.baremetalManager.GetClientSession() + desc, err := modules.Hosts.Create(session, params) + if err != nil { + return nil, err + } + return desc, nil +} + +func (h *DHCPHandler) updateBaremetal(id string) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(h.ClientMac.String()), "access_mac") + params.Add(jsonutils.NewString(h.baremetalManager.GetZoneId()), "zone_id") + params.Add(jsonutils.NewString("baremetal"), "host_type") + params.Add(jsonutils.JSONTrue, "is_baremetal") + session := h.baremetalManager.GetClientSession() + desc, err := modules.Hosts.Update(session, id, params) + if err != nil { + return nil, err + } + return desc, nil +} + +func (h *DHCPHandler) doInitBaremetalAdminNetif(desc jsonutils.JSONObject) error { + var err error + h.baremetalInstance, err = h.baremetalManager.AddBaremetal(desc) + if err != nil { + return err + } + err = h.baremetalInstance.InitAdminNetif(h.ClientMac, h.netConfig, types.NIC_TYPE_ADMIN, models.NETWORK_TYPE_PXE) + return err +} + +func (h *DHCPHandler) isPXERequest() bool { + pkt := h.packet + return dhcp.IsPXERequest(pkt) +} + +func (s *Server) validateDHCP(pkt dhcp.Packet) (Machine, Firmware, error) { + var mach Machine + var fwtype Firmware + fwt, err := pkt.ParseOptions().Uint16(dhcp.OptionClientArchitecture) + if err != nil { + return mach, fwtype, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err) + } + + // Basic architecture and firmware identification, based purely on + // the PXE architecture option. + switch fwt { + // TODO: complete case 1, 2, 3, 4, 5, 8 + case 0: + // Intel x86PC + mach.Arch = ArchIA32 + fwtype = FirmwareX86PC + case 1: + // NEC/PC98 + mach.Arch = ArchUnknown + fwtype = FirmwareUnknown + case 2: + // EFI Itanium + mach.Arch = ArchUnknown + fwtype = FirmwareUnknown + case 3: + // DEC Alpha + mach.Arch = ArchUnknown + fwtype = FirmwareUnknown + case 4: + // Arc x86 + mach.Arch = ArchUnknown + fwtype = FirmwareUnknown + case 5: + // Intel Lean Client + mach.Arch = ArchUnknown + fwtype = FirmwareUnknown + case 6: + // EFI IA32 + mach.Arch = ArchIA32 + fwtype = FirmwareEFI32 + case 7: + // EFI BC + mach.Arch = ArchX64 + fwtype = FirmwareEFI64 + case 8: + // EFI Xscale + mach.Arch = ArchUnknown + fwtype = FirmwareUnknown + case 9: + // EFI x86-64 + mach.Arch = ArchX64 + fwtype = FirmwareEFIBC + default: + return mach, 0, fmt.Errorf("unsupported client firmware type '%d'", fwtype) + } + + guid, _ := pkt.ParseOptions().Bytes(dhcp.OptionClientMachineIdentifier) + switch len(guid) { + case 0: + // A missing GUID is invalid according to the spec, however + // there are PXE ROMs in the wild that omit the GUID and still + // expect to boot. The only thing we do with the GUID is + // mirror it back to the client if it's there, so we might as + // well accept these buggy ROMs. + case 17: + if guid[0] != 0 { + return mach, 0, errors.New("malformed client GUID (option 97), leading byte must be zero") + } + default: + return mach, 0, errors.New("malformed client GUID (option 97), wrong size") + } + + mach.MAC = pkt.CHAddr() + return mach, fwtype, nil +} diff --git a/pkg/baremetal/pxe/doc.go b/pkg/baremetal/pxe/doc.go new file mode 100644 index 0000000000..dbbbc21e32 --- /dev/null +++ b/pkg/baremetal/pxe/doc.go @@ -0,0 +1 @@ +package pxe // import "yunion.io/x/onecloud/pkg/baremetal/pxe" diff --git a/pkg/baremetal/pxe/pxe.go b/pkg/baremetal/pxe/pxe.go new file mode 100644 index 0000000000..d7e609c202 --- /dev/null +++ b/pkg/baremetal/pxe/pxe.go @@ -0,0 +1,134 @@ +package pxe + +import ( + "fmt" + "net" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/dhcp" +) + +const ( + portDHCP = 67 + portTFTP = 69 +) + +// Architecture describes a kind of CPU architecture +type Architecture int + +// Architecture types that pxe knows how to boot +// These architectures are self-reported by the booting machine. The +// machine may support additional execution mode. For example, legacy +// PC BIOS reports itself as an ArchIA32, but may also support ArchX64 +// execution +const ( + // ArchIA32 is a 32-bit x86 machine. It may also support X64 + // execution, but pxe has no way of kowning. + ArchIA32 Architecture = iota + // ArchX64 is a 64-bit x86 machine (aka amd64 aka x64) + ArchX64 + ArchUnknown +) + +func (a Architecture) String() string { + switch a { + case ArchIA32: + return "IA32" + case ArchX64: + return "X64" + default: + return "Unknown architecture" + } +} + +// A Machine describes a machine that is attempting to boot +type Machine struct { + MAC net.HardwareAddr + Arch Architecture +} + +// Firmware describes a kind of firmware attempting to boot. +// This should only be used for selecting the right bootloader within +// pxe, kernel selection should key off the more generic +// Architecture +type Firmware int + +// The bootloaders that pxe knows how to handle +const ( + FirmwareX86PC Firmware = iota // "Classic" x86 BIOS with PXE/UNDI support + FirmwareEFI32 // 32-bit x86 processor running EFI + FirmwareEFI64 // 64-bit x86 processor running EFI + FirmwareEFIBC // 64-bit x86 processor running EFI + FirmwareX86Ipxe // "Classic" x86 BIOS running iPXE (no UNDI support) + FirmwareUnknown +) + +type IBaremetalManager interface { + GetZoneId() string + GetBaremetalByMac(mac net.HardwareAddr) IBaremetalInstance + AddBaremetal(desc jsonutils.JSONObject) (IBaremetalInstance, error) + GetClientSession() *mcclient.ClientSession +} + +type IBaremetalInstance interface { + NeedPXEBoot() bool + GetIPMINic(cliMac net.HardwareAddr) *types.SNic + GetPXEDHCPConfig(arch uint16) (*dhcp.ResponseConfig, error) + GetDHCPConfig(cliMac net.HardwareAddr) (*dhcp.ResponseConfig, error) + InitAdminNetif(cliMac net.HardwareAddr, netConf *types.SNetworkConfig, nicType string, netType string) error + RegisterNetif(cliMac net.HardwareAddr, netConf *types.SNetworkConfig) error + GetTFTPResponse() string +} + +type Server struct { + // Address to listen on, or empty for all interfaces + Address string + DHCPPort int + TFTPPort int + TFTPRootDir string + errs chan error + BaremetalManager IBaremetalManager +} + +func (s *Server) Serve() error { + if s.Address == "" { + s.Address = "0.0.0.0" + } + if s.DHCPPort == 0 { + s.DHCPPort = portDHCP + } + if s.TFTPPort == 0 { + s.TFTPPort = portTFTP + } + + tftpConn, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.TFTPPort)) + if err != nil { + return err + } + tftpHandler, err := NewTFTPHandler(s.TFTPRootDir, s.BaremetalManager) + if err != nil { + return err + } + //tftpSrv := tftp.NewServer(tftpHandler.ReadHandler, nil) + //tftpSrv.SetTimeout(5 * time.Second) + + log.Infof("DHCPServer Bind %s %d", s.Address, s.DHCPPort) + dhcpSrv, _, err := dhcp.NewDHCPServer2(s.Address, s.DHCPPort) + if err != nil { + return err + } + + s.errs = make(chan error) + + dhcpHandler := &DHCPHandler{baremetalManager: s.BaremetalManager} + + go func() { s.errs <- s.serveDHCP(dhcpSrv, dhcpHandler) }() + go func() { s.errs <- s.serveTFTP(tftpConn, tftpHandler) }() + + err = <-s.errs + return err +} diff --git a/pkg/baremetal/pxe/tftp.go b/pkg/baremetal/pxe/tftp.go new file mode 100644 index 0000000000..894ef682ba --- /dev/null +++ b/pkg/baremetal/pxe/tftp.go @@ -0,0 +1,119 @@ +package pxe + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "path/filepath" + "regexp" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/tftp" +) + +var ( + PxeLinuxCfgPattern = `^pxelinux.cfg/01-(?P([0-9a-f]{2}-){5}[0-9a-f]{2})$` +) + +type TFTPHandler struct { + RootDir string + BaremetalManager IBaremetalManager +} + +func NewTFTPHandler(rootDir string, baremetalManager IBaremetalManager) (*TFTPHandler, error) { + if _, err := os.Stat(rootDir); err != nil { + return nil, fmt.Errorf("TFTP root dir %q stat error: %v", rootDir, err) + } + return &TFTPHandler{ + RootDir: rootDir, + BaremetalManager: baremetalManager, + }, nil +} + +// Handle is called when client starts file download from server +func (h *TFTPHandler) Handle(filename string, clientAddr net.Addr) (io.ReadCloser, int64, error) { + regEx := regexp.MustCompile(PxeLinuxCfgPattern) + matches := regEx.FindStringSubmatch(filename) + + if len(matches) != 0 { + paramsMap := make(map[string]string) + // pxelinux config matched + for i, name := range regEx.SubexpNames() { + if i > 0 && i <= len(matches) { + paramsMap[name] = matches[i] + } + } + mac, ok := paramsMap["mac"] + if !ok { + return nil, 0, fmt.Errorf("request filename %q not found mac pattern", filename) + } + macAddr, err := net.ParseMAC(mac) + if err != nil { + return nil, 0, fmt.Errorf("Parse mac string %q error: %v", mac, err) + } + return h.sendPxeLinuxCfgResponse(macAddr, clientAddr) + } + return h.sendFile(filename, clientAddr) +} + +func (h *TFTPHandler) sendPxeLinuxCfgResponse(mac net.HardwareAddr, _ net.Addr) (io.ReadCloser, int64, error) { + log.Debugf("[TFTP] client mac: %s", mac) + bmInstance := h.BaremetalManager.GetBaremetalByMac(mac) + if bmInstance == nil { + err := fmt.Errorf("Not found baremetal instance by mac: %s", mac) + log.Errorf("Get baremetal error: %v", err) + return nil, 0, err + } + respStr := bmInstance.GetTFTPResponse() + log.Debugf("[TFTP] get tftp response config: %s", respStr) + bs := []byte(respStr) + size := int64(len(bs)) + buffer := bytes.NewBufferString(respStr) + + return ioutil.NopCloser(buffer), size, nil +} + +func (h *TFTPHandler) sendFile(filename string, _ net.Addr) (io.ReadCloser, int64, error) { + filename = h.getFilePath(filename) + + st, err := os.Stat(filename) + if err != nil { + log.Errorf("TFTP stat file %q error: %v", filename, err) + return nil, 0, err + } + if !st.Mode().IsRegular() { + return nil, 0, fmt.Errorf("requested path %q is not a file", filename) + } + + file, err := os.Open(filename) + if err != nil { + log.Errorf("TFTP open file %q error: %v", filename, err) + return nil, 0, err + } + return file, st.Size(), err +} + +func (h *TFTPHandler) getFilePath(fileName string) string { + return filepath.Join(h.RootDir, fileName) +} + +func (h *TFTPHandler) transferLog(clientAddr net.Addr, path string, err error) { + log.Debugf("TFTP transfer log clientAddr: %s, path: %s, error: %v", clientAddr, path, err) +} + +func (s *Server) serveTFTP(l net.PacketConn, handler *TFTPHandler) error { + ts := tftp.Server{ + Handler: handler.Handle, + InfoLog: func(msg string) { log.Debugf("TFTP msg: %s", msg) }, + TransferLog: handler.transferLog, + } + err := ts.Serve(l) + if err != nil { + return fmt.Errorf("TFTP server shut down: %v", err) + } + return nil +} diff --git a/pkg/baremetal/service/doc.go b/pkg/baremetal/service/doc.go new file mode 100644 index 0000000000..7dea0c8409 --- /dev/null +++ b/pkg/baremetal/service/doc.go @@ -0,0 +1 @@ +package service // import "yunion.io/x/onecloud/pkg/baremetal/service" diff --git a/pkg/baremetal/service/service.go b/pkg/baremetal/service/service.go new file mode 100644 index 0000000000..a0596ed205 --- /dev/null +++ b/pkg/baremetal/service/service.go @@ -0,0 +1,41 @@ +package service + +import ( + "os" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/baremetal" + "yunion.io/x/onecloud/pkg/baremetal/handler" + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/baremetal/tasks" + "yunion.io/x/onecloud/pkg/cloudcommon" + "yunion.io/x/onecloud/pkg/cloudcommon/service" +) + +type BaremetalService struct { + service.SServiceBase +} + +func New() *BaremetalService { + return &BaremetalService{} +} + +func (s *BaremetalService) StartService() { + cloudcommon.ParseOptions(&o.Options, os.Args, "baremetal.conf", "baremetal") + cloudcommon.InitAuth(&o.Options.CommonOptions, s.startAgent) + + app := cloudcommon.InitApp(&o.Options.CommonOptions, false) + handler.InitHandlers(app) + + cloudcommon.ServeForeverWithCleanup(app, &o.Options.CommonOptions, func() { + tasks.OnStop() + }) +} + +func (s *BaremetalService) startAgent() { + err := baremetal.Start() + if err != nil { + log.Fatalf("Start agent error: %v", err) + } +} diff --git a/pkg/baremetal/status.go b/pkg/baremetal/status.go new file mode 100644 index 0000000000..99ec061bc9 --- /dev/null +++ b/pkg/baremetal/status.go @@ -0,0 +1,29 @@ +package baremetal + +const ( + INIT = "init" + PREPARE = "prepare" + PREPARE_FAIL = "prepare_fail" + READY = "ready" + RUNNING = "running" + MAINTAINING = "maintaining" + START_MAINTAIN = "start_maintain" + DELETING = "deleting" + DELETE = "delete" + DELETE_FAIL = "delete_fail" + UNKNOWN = "unknown" + SYNCING_STATUS = "syncing_status" + SYNC = "sync" + SYNC_FAIL = "sync_fail" + START_CONVERT = "start_convert" + CONVERTING = "converting" + START_FAIL = "start_fail" + STOP_FAIL = "stop_fail" +) + +const ( + NIC_TYPE_IPMI = "ipmi" + NIC_TYPE_ADMIN = "admin" +) + +var NIC_TYPES = []string{NIC_TYPE_ADMIN, NIC_TYPE_IPMI} diff --git a/pkg/baremetal/status/const.go b/pkg/baremetal/status/const.go new file mode 100644 index 0000000000..056efc80ea --- /dev/null +++ b/pkg/baremetal/status/const.go @@ -0,0 +1,29 @@ +package status + +const ( + INIT = "init" + PREPARE = "prepare" + PREPARE_FAIL = "prepare_fail" + READY = "ready" + RUNNING = "running" + MAINTAINING = "maintaining" + START_MAINTAIN = "start_maintain" + DELETING = "deleting" + DELETE = "delete" + DELETE_FAIL = "delete_fail" + UNKNOWN = "unknown" + SYNCING_STATUS = "syncing_status" + SYNC = "sync" + SYNC_FAIL = "sync_fail" + START_CONVERT = "start_convert" + CONVERTING = "converting" + START_FAIL = "start_fail" + STOP_FAIL = "stop_fail" +) + +const ( + SERVER_RUNNING = "running" + SERVER_ADMIN = "admin" + SERVER_READY = "ready" + SERVER_UNKNOWN = "unknown" +) diff --git a/pkg/baremetal/status/doc.go b/pkg/baremetal/status/doc.go new file mode 100644 index 0000000000..b9a200b3fe --- /dev/null +++ b/pkg/baremetal/status/doc.go @@ -0,0 +1 @@ +package status // import "yunion.io/x/onecloud/pkg/baremetal/status" diff --git a/pkg/baremetal/tasks/base.go b/pkg/baremetal/tasks/base.go new file mode 100644 index 0000000000..2506040d65 --- /dev/null +++ b/pkg/baremetal/tasks/base.go @@ -0,0 +1,405 @@ +package tasks + +import ( + "container/list" + "context" + "fmt" + "sync" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type Queue struct { + objList *list.List + objListLock *sync.Mutex +} + +func NewQueue() *Queue { + return &Queue{ + objList: list.New(), + objListLock: new(sync.Mutex), + } +} + +func (q *Queue) Append(obj interface{}) *Queue { + q.objListLock.Lock() + defer q.objListLock.Unlock() + q.objList.PushBack(obj) + return q +} + +func (q *Queue) First() interface{} { + q.objListLock.Lock() + defer q.objListLock.Unlock() + if q.objList.Len() == 0 { + return nil + } + return q.objList.Front().Value +} + +func (q *Queue) IsEmpty() bool { + return q.First() == nil +} + +func (q *Queue) Pop() interface{} { + q.objListLock.Lock() + defer q.objListLock.Unlock() + if q.objList.Len() == 0 { + return nil + } + first := q.objList.Front() + q.objList.Remove(first) + return first.Value +} + +func (q *Queue) String() string { + itemStrings := debugString(q.objList.Front()) + return fmt.Sprintf("%v", itemStrings) +} + +func debugString(elem *list.Element) []string { + if elem == nil { + return nil + } + strings := []string{fmt.Sprintf("%v", elem.Value)} + rest := debugString(elem.Next()) + if rest != nil { + strings = append(strings, rest...) + } + return strings +} + +type TaskQueue struct { + *Queue +} + +type TaskStageFunc func(ctx context.Context, args interface{}) error + +type SSHTaskStageFunc func(ctx context.Context, cli *ssh.Client, args interface{}) error + +type sshStageWrapper struct { + sshStage SSHTaskStageFunc + remoteIP string + password string +} + +func sshStageW( + stage SSHTaskStageFunc, + remoteIP string, + password string, +) *sshStageWrapper { + return &sshStageWrapper{ + sshStage: stage, + remoteIP: remoteIP, + password: password, + } +} + +func (sw *sshStageWrapper) Do(ctx context.Context, args interface{}) error { + cli, err := ssh.NewClient(sw.remoteIP, 22, "root", sw.password, "") + if err != nil { + return err + } + return sw.sshStage(ctx, cli, args) +} + +type ITask interface { + // GetStage return current task stage func + GetStage() TaskStageFunc + // SetStage set task next execute stage func + SetStage(stage TaskStageFunc) + + // GetSSHStage return current task ssh stage func + GetSSHStage() SSHTaskStageFunc + // SetSSHStage set task next execute ssh stage func + SetSSHStage(stage SSHTaskStageFunc) + + // GetTaskId return remote service task id + GetTaskId() string + GetClientSession() *mcclient.ClientSession + + GetTaskQueue() *TaskQueue + // GetData return TaskData from region + GetData() jsonutils.JSONObject + GetName() string + + Execute(ITask ITask, args interface{}) + SetSSHStageParams(task ITask, remoteIP string, passwd string) + SSHExecute(task ITask, remoteIP string, passwd string, args interface{}) + NeedPXEBoot() bool +} + +func NewTaskQueue() *TaskQueue { + return &TaskQueue{ + Queue: NewQueue(), + } +} + +func (q *TaskQueue) GetTask() ITask { + if q.IsEmpty() { + return nil + } + return q.First().(ITask) +} + +func (q *TaskQueue) PopTask() ITask { + if q.IsEmpty() { + return nil + } + return q.Pop().(ITask) +} + +func (q *TaskQueue) AppendTask(task ITask) *TaskQueue { + log.Infof("Append task %s", task.GetName()) + q.Append(task) + return q +} + +type TaskFactory func(bm IBaremetal, taskId string, data jsonutils.JSONObject) (ITask, error) + +type SBaremetalTaskBase struct { + Baremetal IBaremetal + userCred mcclient.TokenCredential + stageFunc TaskStageFunc + sshStageFunc SSHTaskStageFunc + taskId string + data jsonutils.JSONObject +} + +func newBaremetalTaskBase( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalTaskBase { + task := &SBaremetalTaskBase{ + Baremetal: baremetal, + taskId: taskId, + data: data, + } + return task +} + +func (task *SBaremetalTaskBase) GetTaskQueue() *TaskQueue { + return task.Baremetal.GetTaskQueue() +} + +func (task *SBaremetalTaskBase) GetTaskId() string { + return task.taskId +} + +func (task *SBaremetalTaskBase) GetData() jsonutils.JSONObject { + return task.data +} + +func (task *SBaremetalTaskBase) GetStage() TaskStageFunc { + return task.stageFunc +} + +func (task *SBaremetalTaskBase) GetSSHStage() SSHTaskStageFunc { + return task.sshStageFunc +} + +func (task *SBaremetalTaskBase) SetStage(stage TaskStageFunc) { + task.stageFunc = stage +} + +func (task *SBaremetalTaskBase) SetSSHStage(stage SSHTaskStageFunc) { + task.sshStageFunc = stage +} + +func (task *SBaremetalTaskBase) Execute(iTask ITask, args interface{}) { + ExecuteTask(iTask, args) +} + +func (task *SBaremetalTaskBase) SetSSHStageParams(iTask ITask, remoteIP string, password string) { + iTask.SetStage(sshStageW(iTask.GetSSHStage(), remoteIP, password).Do) +} + +func (task *SBaremetalTaskBase) SSHExecute( + iTask ITask, + remoteIP string, + password string, + args interface{}, +) { + //iTask.SetStage(sshStageW(iTask.GetSSHStage(), remoteIP, password).Do) + task.SetSSHStageParams(iTask, remoteIP, password) + ExecuteTask(iTask, args) +} + +//func (task *SBaremetalTaskBase) CallNextStage(iTask ITask, stage TaskStageFunc, args interface{}) { +//iTask.SetStage(stage) +//ExecuteTask(iTask, args) +//} + +func (task *SBaremetalTaskBase) GetClientSession() *mcclient.ClientSession { + return task.Baremetal.GetClientSession() +} + +func (self *SBaremetalTaskBase) EnsurePowerShutdown(soft bool) error { + log.Infof("EnsurePowerShutdown: soft=%v", soft) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + startTime := time.Now() + maxWait := 60 * time.Second + for status == "" || status == types.POWER_STATUS_ON { + if time.Since(startTime).Seconds() >= maxWait.Seconds() && soft { + soft = false + } + err = self.Baremetal.DoPowerShutdown(soft) + if err != nil { + log.Errorf("DoPowerShutdown: %v", err) + } + time.Sleep(20 * time.Second) + status, err = self.Baremetal.GetPowerStatus() + if err != nil { + log.Errorf("GetPowerStatus: %v", err) + } + } + if status != types.POWER_STATUS_OFF { + return fmt.Errorf("Baremetal invalid status %s for shutdown", status) + } + return nil +} + +func (self *SBaremetalTaskBase) EnsurePowerUp(bootdev string) error { + log.Infof("EnsurePowerUp: bootdev=%s", bootdev) + var bootFunc func() error = nil + switch bootdev { + case "pxe": + bootFunc = self.Baremetal.DoPXEBoot + case "disk": + bootFunc = self.Baremetal.DoDiskBoot + } + if bootFunc == nil { + return fmt.Errorf("No boot func %s found", bootdev) + } + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + for status == "" || status == types.POWER_STATUS_OFF { + if status == types.POWER_STATUS_OFF { + err = bootFunc() + if err != nil { + return err + } + } + status, err = self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + if status == "" || status == types.POWER_STATUS_OFF { + time.Sleep(40 * time.Second) + status, err = self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + } + } + if status != types.POWER_STATUS_ON { + return fmt.Errorf("Baremetal invalid restart status: %s", status) + } + return nil +} + +func (self *SBaremetalTaskBase) NeedPXEBoot() bool { + return false +} + +type IPXEBootTask interface { + ITask + OnPXEBoot(ctx context.Context, cli *ssh.Client, args interface{}) error +} + +type SBaremetalPXEBootTaskBase struct { + *SBaremetalTaskBase + pxeBootTask IPXEBootTask + startTime time.Time +} + +func newBaremetalPXEBootTaskBase( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalPXEBootTaskBase { + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + self := &SBaremetalPXEBootTaskBase{ + SBaremetalTaskBase: baseTask, + } + return self + +} + +func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(pxeBootTask IPXEBootTask, data jsonutils.JSONObject) (*SBaremetalPXEBootTaskBase, error) { + self.pxeBootTask = pxeBootTask + //OnInitStage(pxeBootTask) + sshConf, _ := self.Baremetal.GetSSHConfig() + if sshConf != nil && self.Baremetal.TestSSHConfig() { + pxeBootTask.SetSSHStage(pxeBootTask.OnPXEBoot) + pxeBootTask.SetSSHStageParams(pxeBootTask, sshConf.RemoteIP, sshConf.Password) + return self, nil + } + // Do soft reboot + if data != nil && jsonutils.QueryBoolean(data, "soft_boot", false) { + self.startTime = time.Now() + if err := self.Baremetal.DoPowerShutdown(true); err != nil { + log.Errorf("DoPowerShutdown error: %v", err) + } + //self.CallNextStage(self, self.WaitForShutdown, nil) + self.SetStage(self.WaitForShutdown) + + return self, nil + } + // shutdown and power up to PXE mode + if err := self.EnsurePowerShutdown(false); err != nil { + return self, fmt.Errorf("EnsurePowerShutdown: %v", err) + } + if err := self.EnsurePowerUp("pxe"); err != nil { + return self, fmt.Errorf("EnsurePowerUp to pxe: %v", err) + } + // this stage will be called by baremetalInstance when pxe start notify + self.SetSSHStage(pxeBootTask.OnPXEBoot) + return self, nil +} + +func (self *SBaremetalPXEBootTaskBase) NeedPXEBoot() bool { + return true +} + +func (self *SBaremetalPXEBootTaskBase) WaitForShutdown(ctx context.Context, args interface{}) error { + self.SetStage(self.OnStopComplete) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + if status == types.POWER_STATUS_OFF { + self.Execute(self, nil) + } else if time.Since(self.startTime) >= 90*time.Second { + err = self.Baremetal.DoPowerShutdown(false) + if err != nil { + return err + } + } + return nil +} + +func (self *SBaremetalPXEBootTaskBase) OnStopComplete(ctx context.Context, args interface{}) error { + err := self.EnsurePowerUp("pxe") + if err != nil { + return err + } + self.SetSSHStage(self.pxeBootTask.OnPXEBoot) + return nil +} + +func (self *SBaremetalPXEBootTaskBase) GetName() string { + return "BaremetalPXEBootTaskBase" +} diff --git a/pkg/baremetal/tasks/base_test.go b/pkg/baremetal/tasks/base_test.go new file mode 100644 index 0000000000..d4b000d889 --- /dev/null +++ b/pkg/baremetal/tasks/base_test.go @@ -0,0 +1,44 @@ +package tasks + +import ( + "testing" +) + +func TestQueue(t *testing.T) { + type test struct { + queue *Queue + expected string + } + q123 := NewQueue().Append("1").Append("2").Append("3") + q123Pop := NewQueue().Append("1").Append("2").Append("3") + q123Pop.Pop() + qEmptyPop := NewQueue().Append("1").Append("2") + qEmptyPop.Pop() + qEmptyPop.Pop() + qEmptyPop.Pop() + tests := map[string]test{ + "Empty queue": { + queue: NewQueue(), + expected: "[]", + }, + "Queue append": { + queue: q123, + expected: "[1 2 3]", + }, + "Queue pop": { + queue: q123Pop, + expected: "[2 3]", + }, + "Queue pop to empty": { + queue: qEmptyPop, + expected: "[]", + }, + } + for name, testCase := range tests { + output := testCase.queue.String() + expected := testCase.expected + if output != expected { + t.Errorf("TestCase %q failed, output: %v, expected: %v", name, output, expected) + } + } +} diff --git a/pkg/baremetal/tasks/basedeploy.go b/pkg/baremetal/tasks/basedeploy.go new file mode 100644 index 0000000000..04ae235a40 --- /dev/null +++ b/pkg/baremetal/tasks/basedeploy.go @@ -0,0 +1,78 @@ +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type IServerBaseDeployTask interface { + IPXEBootTask + DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) +} + +type SBaremetalServerBaseDeployTask struct { + *SBaremetalPXEBootTaskBase + serverDeployTask IServerBaseDeployTask +} + +func newBaremetalServerBaseDeployTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, + deployTask IServerBaseDeployTask, +) (*SBaremetalServerBaseDeployTask, error) { + task := new(SBaremetalServerBaseDeployTask) + baseTask := newBaremetalPXEBootTaskBase(baremetal, taskId, data) + task.SBaremetalPXEBootTaskBase = baseTask + _, err := baseTask.InitPXEBootTask(task, data) + task.serverDeployTask = deployTask + return task, err +} + +func (self *SBaremetalServerBaseDeployTask) GetName() string { + return "BaremetalServerBaseDeployTask" +} + +func (self *SBaremetalServerBaseDeployTask) GetFinishAction() string { + if self.data != nil { + action, _ := self.data.GetString("on_finish") + return action + } + return "" +} + +func (self *SBaremetalServerBaseDeployTask) OnPXEBoot(ctx context.Context, term *ssh.Client, args interface{}) error { + log.Infof("%s called on stage pxeboot, args: %v", self.GetName(), args) + result, err := self.serverDeployTask.DoDeploys(term) + if err != nil { + return err + } + _, err = term.Run( + "/bin/sync", + "/sbin/sysctl -w vm.drop_caches=3", + ) + if err != nil { + return err + } + onFinishAction := self.GetFinishAction() + if utils.IsInStringArray(onFinishAction, []string{"restart", "shutdown"}) { + err = self.EnsurePowerShutdown(false) + if err != nil { + return err + } + if onFinishAction == "restart" { + err = self.EnsurePowerUp("disk") + if err != nil { + return err + } + } + } + self.Baremetal.AutoSyncAllStatus() + SetTaskComplete(self, result) + return nil +} diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go new file mode 100644 index 0000000000..1e142438f5 --- /dev/null +++ b/pkg/baremetal/tasks/baseprepare.go @@ -0,0 +1,529 @@ +package tasks + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/util/seclib" + + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/baremetal/profiles" + "yunion.io/x/onecloud/pkg/baremetal/utils/detect_storages" + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/onecloud/pkg/util/sysutils" +) + +type sBaremetalPrepareTask struct { + baremetal IBaremetal +} + +func newBaremetalPrepareTask(baremetal IBaremetal) *sBaremetalPrepareTask { + return &sBaremetalPrepareTask{ + baremetal: baremetal, + } +} + +func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { + _, err := cli.Run("/lib/mos/sysinit.sh") + if err != nil { + return err + } + + sysInfo, err := getDMISysinfo(cli) + if err != nil { + return err + } + cpuInfo, err := getCPUInfo(cli) + if err != nil { + return err + } + dmiCPUInfo, err := getDMICPUInfo(cli) + if err != nil { + return err + } + memInfo, err := getDMIMemInfo(cli) + if err != nil { + return err + } + nicsInfo, err := getNicsInfo(cli) + if err != nil { + return err + } + + raidDiskInfo, nonRaidDiskInfo, pcieDiskInfo, err := detect_storages.DetectStorageInfo(cli, true) + if err != nil { + return err + } + diskInfo := make([]*baremetal.BaremetalStorage, 0) + diskInfo = append(diskInfo, raidDiskInfo...) + diskInfo = append(diskInfo, nonRaidDiskInfo...) + diskInfo = append(diskInfo, pcieDiskInfo...) + var storageDriver string + if len(raidDiskInfo) > 0 { + raidDrivers := []string{} + for _, drv := range raidDiskInfo { + raidDrivers = append(raidDrivers, drv.Driver) + } + storageDriver = strings.Join(raidDrivers, ",") + } else { + storageDriver = baremetal.DISK_DRIVER_LINUX + } + + ipmiEnable, err := isIPMIEnable(cli) + if err != nil { + return err + } + + ipmiInfo := &types.SIPMIInfo{ + Present: ipmiEnable, + } + // set ipmi nic DHCP + if ipmiEnable { + sshIPMI := ipmitool.NewSSHIPMI(cli) + // ipmitool.SetSysInfo + ipmiSysInfo := sysInfo.ToIPMISystemInfo() + SetIPMILanPortShared(sshIPMI, ipmiSysInfo) + ipmiUser, ipmiPasswd, ipmiIpAddr := task.getIPMIUserPasswd(ipmiSysInfo) + ipmiInfo.Username = ipmiUser + ipmiInfo.Password = ipmiPasswd + + var ipmiLanChannel int = -1 + for _, lanChannel := range ipmitool.GetLanChannels(ipmiSysInfo) { + log.Infof("Try lan channel %d ...", lanChannel) + conf, err := ipmitool.GetLanConfig(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Get lan channel %d config error: %v", lanChannel, err) + continue + } + if conf.Mac == nil { + log.Errorf("Lan channel %d MAC address is empty", lanChannel) + continue + } + ipmiNic := &types.SNicDevInfo{ + Mac: conf.Mac, + Up: false, + Speed: 100, + Mtu: 1500, + } + task.sendNicInfo(ipmiNic, -1, types.NIC_TYPE_IPMI, true, "") + rootId := ipmitool.GetRootId(ipmiSysInfo) + err = ipmitool.SetLanUserPasswd(sshIPMI, lanChannel, rootId, ipmiUser, ipmiPasswd) + if err != nil { + log.Errorf("Lan channel %d set user password error: %v", lanChannel, err) + } + err = ipmitool.EnableLanAccess(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Lan channel %d enable lan access error: %v", lanChannel, err) + } + + tryAddrs := make([]string, 0) + if ipmiIpAddr != "" { + tryAddrs = append(tryAddrs, ipmiIpAddr) + } + if conf.IPAddr != "" && conf.IPAddr != ipmiIpAddr { + tryAddrs = append(tryAddrs, conf.IPAddr) + } + if len(tryAddrs) > 0 && !o.Options.ForceDhcpProbeIpmi { + for _, tryAddr := range tryAddrs { + log.Infof("IP addr found in IPMI config, try use %s as IPMI address", tryAddr) + ipConf, err := task.getIPMIIPConfig(tryAddr) + if err != nil { + log.Errorf("Failed to get IPMI ipconfig for %s", tryAddr) + continue + } + err = ipmitool.SetLanStatic(sshIPMI, lanChannel, ipConf.IPAddr, ipConf.Netmask, ipConf.Gateway) + if err != nil { + log.Errorf("Failed to set IPMI static net config %#v for %s", *ipConf, tryAddr) + continue + } + time.Sleep(1 * time.Second) + conf, err = ipmitool.GetLanConfig(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Failed to get lan config after set static network: %v", err) + continue + } + log.Infof("Get lan config %#v", *conf) + if conf.IPAddr == "" || conf.IPAddr != tryAddr { + log.Errorf("Failed to set ipmi lan channel %d static ipaddr", lanChannel) + continue + } + rmcpIPMI := ipmitool.NewLanPlusIPMI(tryAddr, ipmiUser, ipmiPasswd) + conf2, err := ipmitool.GetLanConfig(rmcpIPMI, lanChannel) + if err != nil { + log.Errorf("Failed to get lan channel %d config use RMCP mode: %v", lanChannel, err) + continue + } + if len(conf2.Mac) != 0 && + conf2.Mac.String() == conf.Mac.String() && + conf2.IPAddr != "" && conf2.IPAddr == tryAddr { + // make sure the ipaddr is a IPMI address + // enable the netif + if err := task.sendNicInfo(ipmiNic, -1, types.NIC_TYPE_IPMI, false, tryAddr); err != nil { + log.Errorf("Fail to set existing BMC IP address to %s", tryAddr) + } else { + ipmiInfo.IpAddr = tryAddr + ipmiLanChannel = lanChannel + break + } + } else { + log.Errorf("Use RMCP mode get invalid lan config: %#v", conf2) + } + if ipmiLanChannel >= 0 { + // found and set config on lanChannel + break + } + } + } + if len(tryAddrs) > 0 { + task.baremetal.SetExistingIPMIIPAddr(tryAddrs[0]) + } + + err = ipmitool.SetLanDHCP(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Set lan channel %d dhcp error: %v", lanChannel, err) + } + time.Sleep(1 * time.Second) + nic := task.baremetal.GetIPMINic(conf.Mac) + maxTries := 180 // wait 3 minutes + for tried := 0; nic != nil && nic.IpAddr == "" && tried < maxTries; tried++ { + nic = task.baremetal.GetIPMINic(conf.Mac) + } + if len(nic.IpAddr) == 0 { + err = ipmitool.DoBMCReset(sshIPMI) // do BMC reset to force DHCP request + if err != nil { + log.Errorf("Do BMC reset error: %v", err) + } + } + for tried := 0; nic != nil && nic.IpAddr == "" && tried < maxTries; tried++ { + nic = task.baremetal.GetIPMINic(conf.Mac) + } + if nic != nil && len(nic.IpAddr) == 0 { + log.Errorf("DHCP wait IPMI address fail, retry ...") + continue + } + log.Infof("DHCP get IPMI address succ, wait 2 seconds ...") + var tried int = 0 + for tried < maxTries { + time.Sleep(2 * time.Second) + lanConf, err := ipmitool.GetLanConfig(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Get lan config at channel %d error: %v", lanChannel, err) + tried += 2 + continue + } + if lanConf.IPAddr == nic.IpAddr { + break + } + log.Infof("waiting IPMI DHCP address %s %s", lanConf.IPAddr, nic.IpAddr) + tried += 2 + } + if tried >= maxTries { + continue + } + err = ipmitool.SetLanStatic( + sshIPMI, + lanChannel, + nic.IpAddr, + nic.GetNetMask(), + nic.Gateway, + ) + if err != nil { + log.Errorf("Set lanChannel %d static net %#v error: %v", lanChannel, nic, err) + continue + } + ipmiInfo.IpAddr = nic.IpAddr + ipmiLanChannel = lanChannel + } + if ipmiLanChannel == -1 { + return fmt.Errorf("Fail to get IPMI address from DHCP") + } + ipmiInfo.LanChannel = ipmiLanChannel + } + + adminNic := task.baremetal.GetAdminNic() + + // collect params + updateInfo := make(map[string]interface{}) + oname := fmt.Sprintf("BM%s", strings.Replace(adminNic.Mac, ":", "", -1)) + if task.baremetal.GetName() == oname { + updateInfo["name"] = fmt.Sprintf("BM-%s", strings.Replace(ipmiInfo.IpAddr, ".", "-", -1)) + } + updateInfo["access_ip"] = adminNic.IpAddr + updateInfo["cpu_count"] = cpuInfo.Count + updateInfo["node_count"] = dmiCPUInfo.Nodes + updateInfo["cpu_desc"] = cpuInfo.Model + updateInfo["cpu_mhz"] = cpuInfo.Freq + updateInfo["cpu_cache"] = cpuInfo.Cache + updateInfo["mem_size"] = memInfo.Total + updateInfo["storage_driver"] = storageDriver + updateInfo["storage_info"] = diskInfo + updateInfo["sys_info"] = sysInfo + updateInfo["sn"] = sysInfo.SN + size, diskType := task.collectDiskInfo(diskInfo) + updateInfo["storage_size"] = size + updateInfo["storage_type"] = diskType + updateData := jsonutils.Marshal(updateInfo) + updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams()) + _, err = modules.Hosts.Update(task.getClientSession(), task.baremetal.GetId(), updateData) + if err != nil { + log.Errorf("Update baremetal info error: %v", err) + } + if err := task.sendStorageInfo(size); err != nil { + log.Errorf("sendStorageInfo error: %v", err) + } + for i := range nicsInfo { + if nicsInfo[i].Mac.String() == adminNic.GetMac().String() { + if i != 0 { + nicsInfo = append(nicsInfo[i:], nicsInfo[0:i]...) + } + break + } + } + err = task.removeAllNics() + if err != nil { + return err + } + for i := range nicsInfo { + err = task.sendNicInfo(nicsInfo[i], i, "", false, "") + if err != nil { + log.Errorf("Send nicinfo idx: %d, %#v error: %v", i, nicsInfo[i], err) + } + } + for _, nicInfo := range nicsInfo { + if nicInfo.Mac.String() != adminNic.GetMac().String() && nicInfo.Up { + err = task.doNicWireProbe(cli, nicInfo) + if err != nil { + log.Errorf("doNicWireProbe nic %#v error: %v", nicInfo, err) + } + } + } + + log.Infof("Prepare complete") + return nil +} + +func (task *sBaremetalPrepareTask) getIPMIUserPasswd(sysInfo *types.SIPMISystemInfo) (string, string, string) { + var ( + ipmiUser string + ipmiPasswd string + ipmiIpAddr string + ) + ipmiUser = profiles.GetRootName(sysInfo) + isStrongPass := profiles.IsStrongPass(sysInfo) + if !isStrongPass && o.Options.DefaultIpmiPassword != "" { + ipmiPasswd = o.Options.DefaultIpmiPassword + } else if isStrongPass && o.Options.DefaultStrongIpmiPassword != "" { + ipmiPasswd = o.Options.DefaultStrongIpmiPassword + } else if isStrongPass && o.Options.DefaultIpmiPassword != "" { + ipmiPasswd = o.Options.DefaultIpmiPassword + } else { + ipmiPasswd = seclib.RandomPassword(20) + } + oldIPMIConf := task.baremetal.GetRawIPMIConfig() + if oldIPMIConf != nil { + if oldIPMIConf.Username != "" { + ipmiUser = oldIPMIConf.Username + } + if oldIPMIConf.Password != "" { + ipmiPasswd = oldIPMIConf.Password + } + if oldIPMIConf.IpAddr != "" { + ipmiIpAddr = oldIPMIConf.IpAddr + } + } + return ipmiUser, ipmiPasswd, ipmiIpAddr +} + +type ipmiIPConfig struct { + IPAddr string + Netmask string + Gateway string +} + +func (task *sBaremetalPrepareTask) getIPMIIPConfig(ipAddr string) (*ipmiIPConfig, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(ipAddr), "ip") + listRet, err := modules.Networks.List(task.getClientSession(), params) + if err != nil { + return nil, err + } + if len(listRet.Data) != 1 { + return nil, fmt.Errorf("Invalid network list count: %d", len(listRet.Data)) + } + netObj := listRet.Data[0] + config := &ipmiIPConfig{} + config.IPAddr = ipAddr + maskLen, _ := netObj.Int("guest_ip_mask") + config.Netmask = netutils.Masklen2Mask(int8(maskLen)).String() + config.Gateway, _ = netObj.GetString("guest_gateway") + return config, nil +} + +func (task *sBaremetalPrepareTask) getClientSession() *mcclient.ClientSession { + return task.baremetal.GetClientSession() +} + +func (task *sBaremetalPrepareTask) removeAllNics() error { + resp, err := modules.Hosts.PerformAction( + task.getClientSession(), + task.baremetal.GetId(), + "remove-all-netifs", + nil, + ) + if err != nil { + return nil + } + return task.baremetal.SaveDesc(resp) +} + +func getDMISysinfo(cli *ssh.Client) (*types.SDMISystemInfo, error) { + ret, err := cli.Run("/usr/sbin/dmidecode -t 1") + if err != nil { + return nil, err + } + return sysutils.ParseDMISysinfo(ret) +} + +func getCPUInfo(cli *ssh.Client) (*types.SCPUInfo, error) { + ret, err := cli.Run("cat /proc/cpuinfo") + if err != nil { + return nil, err + } + return sysutils.ParseCPUInfo(ret) +} + +func getDMICPUInfo(cli *ssh.Client) (*types.SDMICPUInfo, error) { + ret, err := cli.Run("/usr/sbin/dmidecode -t 4") + if err != nil { + return nil, err + } + return sysutils.ParseDMICPUInfo(ret), nil +} + +func getDMIMemInfo(cli *ssh.Client) (*types.SDMIMemInfo, error) { + ret, err := cli.Run("/usr/sbin/dmidecode -t 17") + if err != nil { + return nil, err + } + return sysutils.ParseDMIMemInfo(ret), nil +} + +func getNicsInfo(cli *ssh.Client) ([]*types.SNicDevInfo, error) { + ret, err := cli.Run("/lib/mos/lsnic") + if err != nil { + return nil, fmt.Errorf("Failed to retrieve NIC info: %v", err) + } + return sysutils.ParseNicInfo(ret), nil +} + +func isIPMIEnable(cli *ssh.Client) (bool, error) { + ret, err := cli.Run("/usr/sbin/dmidecode -t 38") + if err != nil { + return false, fmt.Errorf("Failed to retrieve IPMI info: %v", err) + } + return sysutils.ParseDMIIPMIInfo(ret), nil +} + +func (task *sBaremetalPrepareTask) sendNicInfo(nic *types.SNicDevInfo, idx int, nicType string, reset bool, ipAddr string) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(nic.Mac.String()), "mac") + params.Add(jsonutils.NewInt(int64(nic.Speed)), "rate") + if idx >= 0 { + params.Add(jsonutils.NewInt(int64(idx)), "index") + } + if nicType != "" { + params.Add(jsonutils.NewString(nicType), "nic_type") + } + params.Add(jsonutils.NewInt(int64(nic.Mtu)), "mtu") + params.Add(jsonutils.NewBool(nic.Up), "link_up") + if reset { + params.Add(jsonutils.JSONTrue, "reset") + } + if ipAddr != "" { + params.Add(jsonutils.NewString(ipAddr), "ip_addr") + params.Add(jsonutils.JSONTrue, "require_designated_ip") + } + resp, err := modules.Hosts.PerformAction( + task.getClientSession(), + task.baremetal.GetId(), + "add-netif", + params, + ) + if err != nil { + return err + } + task.baremetal.SaveDesc(resp) + return nil +} + +func (task *sBaremetalPrepareTask) sendStorageInfo(size int64) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewInt(size), "capacity") + params.Add(jsonutils.NewString(task.baremetal.GetZoneId()), "zone_id") + _, err := modules.Hosts.PerformAction(task.getClientSession(), task.baremetal.GetId(), "update-storage", params) + return err +} + +func (task *sBaremetalPrepareTask) doNicWireProbe(cli *ssh.Client, nic *types.SNicDevInfo) error { + maxTries := 6 + for tried := 0; tried < maxTries; tried++ { + log.Infof("doNicWireProbe %v", nic) + _, err := cli.Run(fmt.Sprintf("/sbin/udhcpc -t 1 -T 3 -n -i %s", nic.Dev)) + if err != nil { + log.Errorf("/sbin/udhcpc error: %v", err) + } + nicInfo := task.baremetal.GetNicByMac(nic.Mac) + if nicInfo != nil && nicInfo.WireId != "" { + log.Infof("doNicWireProbe success, get result %#v", nicInfo) + break + } + } + return nil +} + +func (task *sBaremetalPrepareTask) collectDiskInfo(diskInfo []*baremetal.BaremetalStorage) (int64, string) { + cnt := 0 + rotateCnt := 0 + var size int64 = 0 + var diskType string + for _, d := range diskInfo { + if d.Rotate { + rotateCnt += 1 + } + size += d.Size + cnt += 1 + } + if rotateCnt == cnt { + diskType = "rotate" + } else if rotateCnt == 0 { + diskType = "ssd" + } else { + diskType = "hybrid" + } + return size, diskType +} + +func SetIPMILanPortShared(cli ipmitool.IPMIExecutor, sysInfo *types.SIPMISystemInfo) { + if !o.Options.IpmiLanPortShared { + return + } + oemName := strings.ToLower(sysInfo.Manufacture) + var err error + if strings.Contains(oemName, "huawei") { + err = ipmitool.SetHuaweiIPMILanPortShared(cli) + } else if strings.Contains(oemName, "dell") { + err = ipmitool.SetDellIPMILanPortShared(cli) + } + if err != nil { + log.Errorf("Set %s ipmi lan port shared failed: %v", oemName, err) + } +} diff --git a/pkg/baremetal/tasks/create.go b/pkg/baremetal/tasks/create.go new file mode 100644 index 0000000000..a572d7f942 --- /dev/null +++ b/pkg/baremetal/tasks/create.go @@ -0,0 +1,68 @@ +package tasks + +import ( + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerCreateTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerCreateTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalServerCreateTask) + baseTask, err := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task, err +} + +func (self *SBaremetalServerCreateTask) GetName() string { + return "BaremetalServerCreateTask" +} + +func (self *SBaremetalServerCreateTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + // Build raid + err := self.Baremetal.GetServer().DoDiskConfig(term) + if err != nil { + return nil, self.onError(term, err) + } + time.Sleep(2 * time.Second) + if err := self.Baremetal.GetServer().DoEraseDisk(term); err != nil { + return nil, self.onError(term, err) + } + time.Sleep(2 * time.Second) + parts, err := self.Baremetal.GetServer().DoPartitionDisk(term) + if err != nil { + return nil, self.onError(term, err) + } + data := jsonutils.NewDict() + disks, err := self.Baremetal.GetServer().SyncPartitionSize(term, parts) + if err != nil { + return nil, self.onError(term, err) + } + data.Add(jsonutils.Marshal(disks), "disks") + deployInfo, err := self.Baremetal.GetServer().DoDeploy(term, self.data, true) + if err != nil { + return nil, self.onError(term, err) + } + if deployInfo != nil { + data.Update(deployInfo) + } + return data, nil +} + +func (self *SBaremetalServerCreateTask) onError(term *ssh.Client, err error) error { + if err1 := self.Baremetal.GetServer().DoEraseDisk(term); err1 != nil { + log.Warningf("EraseDisk error: %v", err1) + } + self.Baremetal.AutoSyncStatus() + return err +} diff --git a/pkg/baremetal/tasks/deploy.go b/pkg/baremetal/tasks/deploy.go new file mode 100644 index 0000000000..a0faacf8a1 --- /dev/null +++ b/pkg/baremetal/tasks/deploy.go @@ -0,0 +1,30 @@ +package tasks + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerDeployTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerDeployTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalServerDeployTask) + baseTask, err := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task, err +} + +func (self *SBaremetalServerDeployTask) GetName() string { + return "BaremetalServerDeployTask" +} + +func (self *SBaremetalServerDeployTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + return self.Baremetal.GetServer().DoDeploy(term, self.data, false) +} diff --git a/pkg/baremetal/tasks/destroy.go b/pkg/baremetal/tasks/destroy.go new file mode 100644 index 0000000000..e4df5fceae --- /dev/null +++ b/pkg/baremetal/tasks/destroy.go @@ -0,0 +1,38 @@ +package tasks + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerDestroyTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerDestroyTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalServerDestroyTask) + baseTask, err := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task, err +} + +func (self *SBaremetalServerDestroyTask) GetName() string { + return "BaremetalServerDestroyTask" +} + +func (self *SBaremetalServerDestroyTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + if err := self.Baremetal.GetServer().DoEraseDisk(term); err != nil { + log.Errorf("Delete server do erase disk: %v", err) + } + if err := self.Baremetal.GetServer().DoDiskUnconfig(term); err != nil { + log.Errorf("Baremetal do disk unconfig: %v", err) + } + self.Baremetal.RemoveServer() + return nil, nil +} diff --git a/pkg/baremetal/tasks/doc.go b/pkg/baremetal/tasks/doc.go new file mode 100644 index 0000000000..957ed5ea30 --- /dev/null +++ b/pkg/baremetal/tasks/doc.go @@ -0,0 +1 @@ +package tasks // import "yunion.io/x/onecloud/pkg/baremetal/tasks" diff --git a/pkg/baremetal/tasks/interface.go b/pkg/baremetal/tasks/interface.go new file mode 100644 index 0000000000..3b3fcf97e7 --- /dev/null +++ b/pkg/baremetal/tasks/interface.go @@ -0,0 +1,40 @@ +package tasks + +import ( + "net" + + "yunion.io/x/jsonutils" + + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type IBaremetal interface { + GetId() string + GetZoneId() string + GetTaskQueue() *TaskQueue + GetSSHConfig() (*types.SSHConfig, error) + TestSSHConfig() bool + GetAdminNic() *types.SNic + GetName() string + GetClientSession() *mcclient.ClientSession + SaveDesc(desc jsonutils.JSONObject) error + GetNicByMac(net.HardwareAddr) *types.SNic + GetRawIPMIConfig() *types.SIPMIInfo + GetIPMINic(mac net.HardwareAddr) *types.SNic + SetExistingIPMIIPAddr(ipAddr string) + GetServer() baremetaltypes.IBaremetalServer + + SyncStatus(status, reason string) + AutoSyncStatus() + SyncAllStatus(status string) + AutoSyncAllStatus() + + GetPowerStatus() (string, error) + DoPowerShutdown(soft bool) error + DoPXEBoot() error + DoDiskBoot() error + + RemoveServer() +} diff --git a/pkg/baremetal/tasks/maintenance.go b/pkg/baremetal/tasks/maintenance.go new file mode 100644 index 0000000000..989bf0d94a --- /dev/null +++ b/pkg/baremetal/tasks/maintenance.go @@ -0,0 +1,40 @@ +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalMaintenanceTask struct { + *SBaremetalPXEBootTaskBase +} + +func NewBaremetalMaintenanceTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalMaintenanceTask) + baseTask := newBaremetalPXEBootTaskBase(baremetal, taskId, data) + task.SBaremetalPXEBootTaskBase = baseTask + _, err := baseTask.InitPXEBootTask(task, data) + return task, err +} + +func (self *SBaremetalMaintenanceTask) OnPXEBoot(ctx context.Context, term *ssh.Client, args interface{}) error { + sshConfig := term.GetConfig() + dataObj := map[string]interface{}{ + "username": sshConfig.Username, + "password": sshConfig.Password, + "ip": sshConfig.Host, + } + if jsonutils.QueryBoolean(self.data, "guest_running", false) { + dataObj["guest_running"] = true + } + self.Baremetal.AutoSyncStatus() + SetTaskComplete(self, jsonutils.Marshal(dataObj)) + return nil +} diff --git a/pkg/baremetal/tasks/prepare.go b/pkg/baremetal/tasks/prepare.go new file mode 100644 index 0000000000..6fe83cbf15 --- /dev/null +++ b/pkg/baremetal/tasks/prepare.go @@ -0,0 +1,46 @@ +package tasks + +import ( + "context" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/baremetal/status" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerPrepareTask struct { + *SBaremetalTaskBase +} + +func NewBaremetalServerPrepareTask( + baremetal IBaremetal, +) *SBaremetalServerPrepareTask { + baseTask := newBaremetalTaskBase(baremetal, "", nil) + task := &SBaremetalServerPrepareTask{ + SBaremetalTaskBase: baseTask, + } + task.SetSSHStage(task.OnPXEBootRequest) + return task +} + +func (self *SBaremetalServerPrepareTask) NeedPXEBoot() bool { + return true +} + +func (self *SBaremetalServerPrepareTask) GetName() string { + return "BaremetalServerPrepareTask" +} + +// OnPXEBootRequest called by notify api handler +func (self *SBaremetalServerPrepareTask) OnPXEBootRequest(ctx context.Context, cli *ssh.Client, args interface{}) error { + err := newBaremetalPrepareTask(self.Baremetal).DoPrepare(cli) + if err != nil { + log.Errorf("Prepare failed: %v", err) + self.Baremetal.SyncStatus(status.PREPARE_FAIL, err.Error()) + return err + } + self.Baremetal.AutoSyncStatus() + SetTaskComplete(self, nil) + return nil +} diff --git a/pkg/baremetal/tasks/rebuild.go b/pkg/baremetal/tasks/rebuild.go new file mode 100644 index 0000000000..7fd998bf11 --- /dev/null +++ b/pkg/baremetal/tasks/rebuild.go @@ -0,0 +1,47 @@ +package tasks + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerRebuildTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerRebuildTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalServerRebuildTask) + baseTask, err := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task, err +} + +func (self *SBaremetalServerRebuildTask) GetName() string { + return "BaremetalServerRebuildTask" +} + +func (self *SBaremetalServerRebuildTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + parts, err := self.Baremetal.GetServer().DoRebuildRootDisk(term) + if err != nil { + return nil, fmt.Errorf("Rebuild root disk: %v", err) + } + disks, err := self.Baremetal.GetServer().SyncPartitionSize(term, parts) + if err != nil { + return nil, fmt.Errorf("SyncPartitionSize: %v", err) + } + data := jsonutils.NewDict() + data.Add(jsonutils.NewArray(disks...), "disks") + deployInfo, err := self.Baremetal.GetServer().DoDeploy(term, data, false) + if err != nil { + return nil, fmt.Errorf("DoDeploy: %v", err) + } + data.Update(deployInfo) + return data, nil +} diff --git a/pkg/baremetal/tasks/reprepare.go b/pkg/baremetal/tasks/reprepare.go new file mode 100644 index 0000000000..32a46668d7 --- /dev/null +++ b/pkg/baremetal/tasks/reprepare.go @@ -0,0 +1,32 @@ +package tasks + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalReprepareTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalReprepareTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalReprepareTask) + baseTask, err := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task, err +} + +func (self *SBaremetalReprepareTask) GetName() string { + return "BaremetalReprepareTask" +} + +func (self *SBaremetalReprepareTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + task := newBaremetalPrepareTask(self.Baremetal) + err := task.DoPrepare(term) + return nil, err +} diff --git a/pkg/baremetal/tasks/resetbmc.go b/pkg/baremetal/tasks/resetbmc.go new file mode 100644 index 0000000000..3e22b37b95 --- /dev/null +++ b/pkg/baremetal/tasks/resetbmc.go @@ -0,0 +1,67 @@ +package tasks + +import ( + "context" + "time" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalResetBMCTask struct { + *SBaremetalPXEBootTaskBase + term *ssh.Client +} + +func NewBaremetalResetBMCTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalResetBMCTask) + baseTask := newBaremetalPXEBootTaskBase(baremetal, taskId, data) + task.SBaremetalPXEBootTaskBase = baseTask + _, err := baseTask.InitPXEBootTask(task, data) + return task, err +} + +func (self *SBaremetalResetBMCTask) GetName() string { + return "BaremetalResetBMCTask" +} + +func (self *SBaremetalResetBMCTask) GetIPMITool() *ipmitool.SSHIPMI { + return ipmitool.NewSSHIPMI(self.term) +} + +func (self *SBaremetalResetBMCTask) OnPXEBoot(ctx context.Context, term *ssh.Client, args interface{}) error { + self.term = term + self.SetStage(self.WaitForBMCReady) + err := ipmitool.DoBMCReset(self.GetIPMITool()) + if err != nil { + return err + } + time.Sleep(10 * time.Second) + ExecuteTask(self, nil) + return nil +} + +func (self *SBaremetalResetBMCTask) WaitForBMCReady(ctx context.Context, args interface{}) error { + self.SetStage(self.OnBMCReady) + status, err := ipmitool.GetChassisPowerStatus(self.GetIPMITool()) + if err != nil { + return err + } + if status != "" && status == types.POWER_STATUS_ON { + ExecuteTask(self, nil) + } + return nil +} + +func (self *SBaremetalResetBMCTask) OnBMCReady(ctx context.Context, args interface{}) error { + time.Sleep(20 * time.Second) + SetTaskComplete(self, nil) + return nil +} diff --git a/pkg/baremetal/tasks/start.go b/pkg/baremetal/tasks/start.go new file mode 100644 index 0000000000..94d431bf4a --- /dev/null +++ b/pkg/baremetal/tasks/start.go @@ -0,0 +1,55 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" +) + +type SBaremetalServerStartTask struct { + *SBaremetalTaskBase +} + +func NewBaremetalServerStartTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + self := &SBaremetalServerStartTask{ + SBaremetalTaskBase: baseTask, + } + if err := self.Baremetal.DoDiskBoot(); err != nil { + return nil, fmt.Errorf("DoDiskBoot: %v", err) + } + self.SetStage(self.WaitForStart) + ExecuteTask(self, nil) + return self, nil +} + +func (self *SBaremetalServerStartTask) GetName() string { + return "BaremetalServerStartTask" +} + +func (self *SBaremetalServerStartTask) WaitForStart(ctx context.Context, args interface{}) error { + self.SetStage(self.OnStartComplete) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return fmt.Errorf("GetPowerStatus: %v", err) + } + log.Infof("%s WaitForStart status=%s", self.GetName(), status) + if status == types.POWER_STATUS_ON { + ExecuteTask(self, nil) + } + return nil +} + +func (self *SBaremetalServerStartTask) OnStartComplete(ctx context.Context, args interface{}) error { + self.Baremetal.SyncAllStatus(types.POWER_STATUS_ON) + SetTaskComplete(self, nil) + return nil +} diff --git a/pkg/baremetal/tasks/stop.go b/pkg/baremetal/tasks/stop.go new file mode 100644 index 0000000000..ebd880d9be --- /dev/null +++ b/pkg/baremetal/tasks/stop.go @@ -0,0 +1,59 @@ +package tasks + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" +) + +type SBaremetalServerStopTask struct { + *SBaremetalTaskBase + startTime time.Time +} + +func NewBaremetalServerStopTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + self := &SBaremetalServerStopTask{ + SBaremetalTaskBase: baseTask, + } + if err := self.Baremetal.DoPowerShutdown(true); err != nil { + return nil, fmt.Errorf("Do power shutdown error: %v", err) + } + self.startTime = time.Now() + self.SetStage(self.WaitForStop) + ExecuteTask(self, nil) + return self, nil +} + +func (self *SBaremetalServerStopTask) GetName() string { + return "BaremetalServerStopTask" +} + +func (self *SBaremetalServerStopTask) WaitForStop(ctx context.Context, args interface{}) error { + self.SetStage(self.OnStopComplete) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return fmt.Errorf("GetPowerStatus: %v", err) + } + if status == types.POWER_STATUS_OFF { + ExecuteTask(self, nil) + } else if time.Since(self.startTime) >= 90*time.Second { + if err := self.Baremetal.DoPowerShutdown(false); err != nil { + return err + } + } + return nil +} + +func (self *SBaremetalServerStopTask) OnStopComplete(ctx context.Context, args interface{}) error { + SetTaskComplete(self, nil) + return nil +} diff --git a/pkg/baremetal/tasks/unmaintenance.go b/pkg/baremetal/tasks/unmaintenance.go new file mode 100644 index 0000000000..b1aa938ad9 --- /dev/null +++ b/pkg/baremetal/tasks/unmaintenance.go @@ -0,0 +1,68 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + baremetalstatus "yunion.io/x/onecloud/pkg/baremetal/status" + "yunion.io/x/onecloud/pkg/cloudcommon/types" +) + +type SBaremetalUnmaintenanceTask struct { + *SBaremetalTaskBase +} + +func NewBaremetalUnmaintenanceTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (ITask, error) { + task := new(SBaremetalUnmaintenanceTask) + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + task.SBaremetalTaskBase = baseTask + var err error + if jsonutils.QueryBoolean(task.data, "guest_running", false) { + err = task.EnsurePowerShutdown(false) + if err != nil { + return task, fmt.Errorf("EnsurePowerShutdown hard: %v", err) + } + err = task.EnsurePowerUp("disk") + if err != nil { + return task, fmt.Errorf("EnsurePowerUp disk: %v", err) + } + task.Baremetal.SyncStatus(baremetalstatus.RUNNING, "") + SetTaskComplete(task, nil) + return task, nil + } + task.SetStage(task.WaitForStop) + err = task.EnsurePowerShutdown(true) + if err != nil { + return task, fmt.Errorf("EnsurePowerShutdown soft: %v", err) + } + ExecuteTask(task, nil) + return task, nil +} + +func (self *SBaremetalUnmaintenanceTask) WaitForStop(ctx context.Context, args interface{}) error { + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + self.SetStage(self.OnStopComplete) + if status == types.POWER_STATUS_OFF { + ExecuteTask(self, nil) + } + return nil +} + +func (self *SBaremetalUnmaintenanceTask) OnStopComplete(ctx context.Context, args interface{}) error { + self.Baremetal.SyncStatus(baremetalstatus.READY, "") + SetTaskComplete(self, nil) + return nil +} + +func (self *SBaremetalUnmaintenanceTask) GetName() string { + return "BaremetalUnmaintenanceTask" +} diff --git a/pkg/baremetal/tasks/worker.go b/pkg/baremetal/tasks/worker.go new file mode 100644 index 0000000000..9ed3226553 --- /dev/null +++ b/pkg/baremetal/tasks/worker.go @@ -0,0 +1,87 @@ +package tasks + +import ( + "context" + "fmt" + "runtime/debug" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var baremetalTaskWorkerMan *appsrv.SWorkerManager + +func init() { + baremetalTaskWorkerMan = appsrv.NewWorkerManager("BaremetalTaskWorkerManager", 8, 1024, false) +} + +func GetWorkManager() *appsrv.SWorkerManager { + return baremetalTaskWorkerMan +} + +func OnStop() { + for GetWorkManager().ActiveWorkerCount() > 0 { + log.Warningf("Busy workers count %d, waiting them finish", GetWorkManager().ActiveWorkerCount()) + time.Sleep(5 * time.Second) + } +} + +func ExecuteTask(task ITask, args interface{}) { + baremetalTaskWorkerMan.Run(func() { + executeTask(task, args) + }, nil, nil) +} + +func executeTask(task ITask, args interface{}) { + if task == nil { + return + } + curStage := task.GetStage() + if curStage == nil { + return + } + defer func() { + if err := recover(); err != nil { + log.Errorf("Execute task panic: %v", err) + debug.PrintStack() + SetTaskFail(task, fmt.Errorf("%v", err)) + } + }() + err := curStage(context.Background(), args) + if err != nil { + log.Errorf("Execute task %s error: %v", task.GetName(), err) + SetTaskFail(task, err) + } +} + +func SetTaskComplete(task ITask, data jsonutils.JSONObject) { + taskId := task.GetTaskId() + if taskId != "" { + session := task.GetClientSession() + modules.ComputeTasks.TaskComplete(session, taskId, data) + } + onTaskEnd(task) +} + +func SetTaskFail(task ITask, err error) { + taskId := task.GetTaskId() + if taskId != "" { + session := task.GetClientSession() + modules.ComputeTasks.TaskFailed(session, taskId, err) + } + onTaskEnd(task) +} + +func onTaskEnd(task ITask) { + task.SetStage(nil) + ExecuteTask(task.GetTaskQueue().PopTask(), nil) +} + +func OnInitStage(task ITask) error { + log.Infof("Start task %s", task.GetName()) + return nil +} diff --git a/pkg/baremetal/types/doc.go b/pkg/baremetal/types/doc.go new file mode 100644 index 0000000000..cf70c34158 --- /dev/null +++ b/pkg/baremetal/types/doc.go @@ -0,0 +1 @@ +package types // import "yunion.io/x/onecloud/pkg/baremetal/types" diff --git a/pkg/baremetal/types/types.go b/pkg/baremetal/types/types.go new file mode 100644 index 0000000000..d0d85354ff --- /dev/null +++ b/pkg/baremetal/types/types.go @@ -0,0 +1,26 @@ +package types + +import ( + "net" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type IBaremetalServer interface { + GetName() string + GetId() string + RemoveDesc() + DoDiskUnconfig(term *ssh.Client) error + DoDiskConfig(term *ssh.Client) error + DoEraseDisk(term *ssh.Client) error + DoPartitionDisk(term *ssh.Client) ([]*disktool.Partition, error) + DoRebuildRootDisk(term *ssh.Client) ([]*disktool.Partition, error) + SyncPartitionSize(term *ssh.Client, parts []*disktool.Partition) ([]jsonutils.JSONObject, error) + DoDeploy(term *ssh.Client, data jsonutils.JSONObject, isInit bool) (jsonutils.JSONObject, error) + SaveDesc(desc jsonutils.JSONObject) error + GetNicByMac(mac net.HardwareAddr) *types.SNic +} diff --git a/pkg/baremetal/utils/detect_storages/detect_storages.go b/pkg/baremetal/utils/detect_storages/detect_storages.go new file mode 100644 index 0000000000..97e3e09075 --- /dev/null +++ b/pkg/baremetal/utils/detect_storages/detect_storages.go @@ -0,0 +1,117 @@ +package detect_storages + +import ( + "fmt" + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/baremetal/utils/raid" + "yunion.io/x/onecloud/pkg/baremetal/utils/raid/drivers" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/onecloud/pkg/util/sysutils" +) + +func GetRaidDevices(drv raid.IRaidDriver) []*baremetal.BaremetalStorage { + devs := make([]*baremetal.BaremetalStorage, 0) + for _, ada := range drv.GetAdapters() { + devs = append(devs, ada.GetDevices()...) + } + return devs +} + +func GetRaidLogicVolumes(drv raid.IRaidDriver) ([]int, error) { + lvs := []int{} + for _, adapter := range drv.GetAdapters() { + lv, err := adapter.GetLogicVolumes() + if err != nil { + return nil, err + } + lvs = append(lvs, lv...) + } + return lvs, nil +} + +func DetectStorageInfo(term *ssh.Client, wait bool) ([]*baremetal.BaremetalStorage, []*baremetal.BaremetalStorage, []*baremetal.BaremetalStorage, error) { + raidDiskInfo := make([]*baremetal.BaremetalStorage, 0) + lvDiskInfo := make([]int, 0) + + raidDrivers := []string{} + for _, drv := range drivers.GetDrivers(term) { + if err := drv.ParsePhyDevs(); err != nil { + log.V(2).Warningf("ParsePhyDevs: %v", err) + continue + } + raidDiskInfo = append(raidDiskInfo, GetRaidDevices(drv)...) + if drv.GetName() == baremetal.DISK_DRIVER_MARVELRAID { + lvs, err := GetRaidLogicVolumes(drv) + if err != nil { + log.Errorf("GetRaidLogicVolumes: %v", err) + } else { + lvDiskInfo = append(lvDiskInfo, lvs...) + } + } + raidDrivers = append(raidDrivers, drv.GetName()) + } + + log.Infof("Get Raid drivers: %v", raidDrivers) + + pcieRet, err := term.Run("/lib/mos/lsdisk --pcie") + if err != nil { + return nil, nil, nil, fmt.Errorf("Fail to retrieve PCIE DISK info") + } + pcieDiskInfo := sysutils.ParsePCIEDiskInfo(pcieRet) + + maxTries := 6 + sleep := 10 * time.Second + nonRaidDiskInfo := []*types.SDiskInfo{} + for tried := 0; len(nonRaidDiskInfo) <= len(lvDiskInfo) && tried < maxTries; tried++ { + ret, err := term.Run("/lib/mos/lsdisk --nonraid") + if err != nil { + return nil, nil, nil, fmt.Errorf("Fail to retrieve Non-Raid SCSI DISK info") + } + nonRaidDiskInfo = sysutils.ParseSCSIDiskInfo(ret) + if wait { + time.Sleep(sleep) + } else { + break + } + } + log.Infof("RaidDiskInfo: %#v, NonRaidSCSIDiskInfo: %#v, PCIEDiskInfo: %#v", raidDiskInfo, nonRaidDiskInfo, pcieDiskInfo) + if len(nonRaidDiskInfo) < len(lvDiskInfo) { + return nil, nil, nil, fmt.Errorf("Fail to retrieve disk info") + } + if len(lvDiskInfo) > 0 { + if len(lvDiskInfo) >= len(nonRaidDiskInfo) { + nonRaidDiskInfo = nil + } else { + nonRaidDiskInfo = nonRaidDiskInfo[:len(nonRaidDiskInfo)-len(lvDiskInfo)] + } + } + + return raidDiskInfo, convertDiskInfos(nonRaidDiskInfo), convertDiskInfos(pcieDiskInfo), nil +} + +func convertDiskInfos(infos []*types.SDiskInfo) []*baremetal.BaremetalStorage { + ret := make([]*baremetal.BaremetalStorage, 0) + for _, info := range infos { + ret = append(ret, convertDiskInfo(info)) + } + return ret +} + +func convertDiskInfo(info *types.SDiskInfo) *baremetal.BaremetalStorage { + return &baremetal.BaremetalStorage{ + Driver: info.Driver, + Size: int64(info.Size), + Rotate: info.Rotate, + Dev: info.Dev, + Sector: info.Sector, + Block: info.Block, + ModuleInfo: info.ModuleInfo, + Kernel: info.Kernel, + PCIClass: info.PCIClass, + } +} diff --git a/pkg/baremetal/utils/detect_storages/doc.go b/pkg/baremetal/utils/detect_storages/doc.go new file mode 100644 index 0000000000..5b3f00e35a --- /dev/null +++ b/pkg/baremetal/utils/detect_storages/doc.go @@ -0,0 +1 @@ +package detect_storages // import "yunion.io/x/onecloud/pkg/baremetal/utils/detect_storages" diff --git a/pkg/baremetal/utils/disktool/disktool.go b/pkg/baremetal/utils/disktool/disktool.go new file mode 100644 index 0000000000..b95d4971b5 --- /dev/null +++ b/pkg/baremetal/utils/disktool/disktool.go @@ -0,0 +1,668 @@ +package disktool + +import ( + "fmt" + "math" + "strings" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/baremetal" + fileutils "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/onecloud/pkg/util/sysutils" +) + +const ( + // MB_SECTORS = 2048 // 1MiB = 2014 sectors + GPT_SECTORS = 34 + + RAID_DRVIER = "raid" + NONRAID_DRIVER = "nonraid" + PCIE_DRIVER = "pcie" + + LABEL_MSDOS = "msdos" + LABEL_GPT = "gpt" +) + +type Partition struct { + disk *DiskPartitions + index int + bootable bool + start int64 + end int64 + count int64 + diskType string + fs string + dev string +} + +func NewPartition( + disk *DiskPartitions, + index int, bootable bool, + start int64, end int64, count int64, + diskType string, fs string, dev string, +) *Partition { + return &Partition{ + disk: disk, + index: index, + bootable: bootable, + start: start, + end: end, + count: count, + diskType: diskType, + fs: fs, + dev: dev, + } +} + +func (p *Partition) GetStart() int64 { + return p.start +} + +func (p *Partition) GetEnd() int64 { + return p.end +} + +func (p *Partition) GetDev() string { + return p.dev +} + +func (p *Partition) String() string { + bootStr := "" + if p.bootable { + bootStr = " boot" + } + return fmt.Sprintf("%s %d %d %s %s%s", p.dev, p.start, p.end, p.diskType, p.fs, bootStr) +} + +func (p *Partition) Format(fs string, uuid string) error { + cmd := []string{} + cmdUUID := []string{} + switch fs { + case "swap": + cmd = []string{"/sbin/mkswap", "-U", uuid} + case "ext2": + cmd = []string{"/usr/sbin/mkfs.ext2"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "ext3": + cmd = []string{"/usr/sbin/mkfs.ext3"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "ext4": + cmd = []string{"/usr/sbin/mkfs.ext4", "-O", "^64bit", "-E", "lazy_itable_init=1"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "ext4dev": + cmd = []string{"/usr/sbin/mkfs.ext4dev", "-E", "lazy_itable_init=1"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "xfs": + cmd = []string{"/sbin/mkfs.xfs", "-f", "-m", "crc=0", "-i", "projid32bit=0", "-n", "ftype=0"} + cmdUUID = []string{"PATH=/bin:/sbin:/usr/bin:/usr/sbin /usr/sbin/xfs_admin", "-U", uuid} + default: + return fmt.Errorf("Unsupported filesystem %s", fs) + } + cmd = append(cmd, p.dev) + cmds := []string{strings.Join(cmd, " ")} + if len(cmdUUID) != 0 { + cmdUUID = append(cmdUUID, p.dev) + cmds = append(cmds, strings.Join(cmdUUID, " ")) + } + _, err := p.Run(cmds...) + return err +} + +func (p *Partition) Fsck() error { + if p.fs == "" { + return fmt.Errorf("filesystem is empty") + } + cmd := []string{} + if strings.HasPrefix(p.fs, "ext") { + cmd = []string{fmt.Sprintf("/usr/sbin/fsck.%s", p.fs), "-f", "-p"} + } else if p.fs == "xfs" { + cmd = []string{"/sbin/fsck.xfs"} + } else { + return fmt.Errorf("Unsupported fsck filesystem: %s", p.fs) + } + cmd = append(cmd, p.dev) + _, err := p.Run(strings.Join(cmd, " ")) + return err +} + +func (p *Partition) Run(cmds ...string) ([]string, error) { + return p.disk.Run(cmds...) +} + +func (p *Partition) ResizeFs() error { + if p.fs == "" { + return nil + } + cmd := []string{} + if strings.HasPrefix(p.fs, "linux-swap") { + cmd = []string{"/sbin/mkswap", p.dev} + } else if strings.HasPrefix(p.fs, "ext") { + if err := p.Fsck(); err != nil { + log.Warningf("FSCK error: %v", err) + } + cmd = []string{"/usr/sbin/resize2fs", p.dev} + } else if p.fs == "xfs" { + return p.ResizeXfs() + } + if len(cmd) == 0 { + return nil + } + _, err := p.Run(strings.Join(cmd, " ")) + return err +} + +func (p *Partition) ResizeXfs() error { + mountPath := fmt.Sprintf("/tmp/%s", strings.Replace(p.dev, "/", "", -1)) + cmds := []string{ + fmt.Sprintf("mkdir -p %s", mountPath), + fmt.Sprintf("mount -t xfs %s %s", p.dev, mountPath), + fmt.Sprintf("/usr/sbin/xfs_growfs -d %s", mountPath), + fmt.Sprintf("umount %s", mountPath), + fmt.Sprintf("rm -fr %s", mountPath), + } + _, err := p.Run(cmds...) + if err != nil { + log.Errorf("Resize xfs error: %v", err) + cmds = []string{ + fmt.Sprintf("umount %s", mountPath), + fmt.Sprintf("rm -rf %s", mountPath), + } + _, err = p.Run(cmds...) + if err != nil { + log.Errorf("Umount error: %v", err) + return err + } + } + return nil +} + +func (p *Partition) GetSizeMB() (int64, error) { + log.Infof("GetSizeMB: start %d, end: %d, count: %d", p.start, p.end, p.count) + if p.count != (p.end - p.start + 1) { + return 0, fmt.Errorf("Count(%d) != End(%d)-Start(%d)+1", p.count, p.end, p.start) + } + return p.count * 512 / 1024 / 1024, nil +} + +type DiskPartitions struct { + driver string + adapter int + sizeMB int64 // MB + tool *PartitionTool + dev string + devName string + sectors int64 + blockSize int64 + rotate bool + desc string + label string + partitions []*Partition +} + +func newDiskPartitions(driver string, adapter int, sizeMB int64, blockSize int64, tool *PartitionTool) *DiskPartitions { + ps := new(DiskPartitions) + ps.driver = driver + ps.adapter = adapter + ps.sizeMB = sizeMB + ps.tool = tool + ps.blockSize = blockSize + ps.partitions = make([]*Partition, 0) + return ps +} + +func (p *DiskPartitions) SetInfo(info *types.SDiskInfo) *DiskPartitions { + p.dev = fmt.Sprintf("/dev/%s", info.Dev) + p.devName = info.Dev + p.sectors = info.Sector + p.desc = info.ModuleInfo + p.blockSize = info.Block + if p.blockSize == 4096 { + p.sectors = (p.sectors >> 3) + } + return p +} + +func (ps *DiskPartitions) MBSectors() int64 { + return int64(1024 * 1024 / ps.blockSize) +} + +func (ps *DiskPartitions) String() string { + return fmt.Sprintf("%s %d %s", ps.devName, ps.sizeMB, ps.driver) +} + +func (ps *DiskPartitions) DebugString() string { + partitionsStr := []string{} + for _, p := range ps.partitions { + partitionsStr = append(partitionsStr, fmt.Sprintf("%#v", *p)) + } + return fmt.Sprintf("driver: %s, dev: %s, sectors: %d, partitions: %#v", ps.driver, ps.dev, ps.sectors, partitionsStr) +} + +func (ps *DiskPartitions) IsReady() bool { + if ps.dev == "" { + return false + } + return true +} + +func (ps *DiskPartitions) GetDevName() string { + return ps.devName +} + +func (ps *DiskPartitions) RetrievePartitionInfo() error { + ps.partitions = make([]*Partition, 0) + cmd := []string{"/usr/sbin/parted", "-s", ps.dev, "--", "unit", "s", "print"} + ret, err := ps.Run(strings.Join(cmd, " ")) + if err != nil { + return err + } + parts, label := fileutils.ParseDiskPartitions(ps.dev, ret) + ps.label = label + for _, part := range parts { + ps.addPartition(part) + } + return nil +} + +func (ps *DiskPartitions) addPartition(p fileutils.Partition) { + part := NewPartition(ps, p.Index, p.Bootable, p.Start, p.End, p.Count, p.DiskType, p.Fs, p.DevName) + ps.partitions = append(ps.partitions, part) +} + +func (ps *DiskPartitions) GPTEndSector() int64 { + return ps.sectors - int64(GPT_SECTORS) +} + +func (ps *DiskPartitions) FsToTypeCode(fs string) string { + if strings.Contains(fs, "swap") { + return "8200" + } else if strings.HasPrefix(fs, "ntfs") || strings.HasPrefix(fs, "fat") { + return "0700" + } + return "8300" +} + +func (ps *DiskPartitions) doResize(dev string, cmd string) error { + cmds := []string{} + cmds = append(cmds, cmd) + cmds = append(cmds, fmt.Sprintf("/sbin/hdparm -f %s", dev)) + cmds = append(cmds, fmt.Sprintf("/sbin/hdparm -z %s", dev)) + _, err := ps.tool.Run(cmds...) + if err != nil { + return err + } + return ps.RetrievePartitionInfo() +} + +func (ps *DiskPartitions) ResizePartition(offsetMB int64) error { + if len(ps.partitions) == 0 { + return fmt.Errorf("ResizePartitions error: total %d partitions", len(ps.partitions)) + } + var cmd string + if ps.label == LABEL_MSDOS { + part := ps.partitions[len(ps.partitions)-1] + if part.diskType == "extended" { + log.Infof("Find last partition an empty extended partition, removed it") + cmd := fmt.Sprintf("/usr/sbin/parted -a none -s %s -- rm %d", part.disk.dev, part.index) + if err := ps.doResize(part.disk.dev, cmd); err != nil { + return fmt.Errorf("Fail to remove empty extended partition: %v", err) + } + } + } + part := ps.partitions[len(ps.partitions)-1] + var end int64 + if offsetMB <= 0 { + end = ps.GPTEndSector() + } else { + end = offsetMB*ps.MBSectors() - 1 + if end > ps.GPTEndSector() { + end = ps.GPTEndSector() + } + } + if end < part.end { + log.Warningf("Cannot reduce size %d %d, no need to resize", end, part.end) + end = part.end + } + if ps.label == LABEL_MSDOS { + if part.diskType == "logical" { + extendIdx := -1 + for i := range ps.partitions { + if ps.partitions[i].diskType == "extended" { + log.Infof("Find extended at %d", i) + extendIdx = i + break + } + } + if extendIdx < 0 { + return fmt.Errorf("To resize logical parition, but fail to find extend partiton") + } + cmd = fmt.Sprintf("/usr/sbin/parted -a none -s %s -- unit s", part.disk.dev) + partsLen := len(ps.partitions) + for i := partsLen - 1; i > extendIdx-1; i-- { + cmd = fmt.Sprintf("%s rm %d", cmd, ps.partitions[i].index) + } + for i := extendIdx; i < partsLen; i++ { + cmdLet := fmt.Sprintf("mkpart %s %d", ps.partitions[i].diskType, ps.partitions[i].start) + if i == extendIdx { + cmdLet = fmt.Sprintf("%s %d", cmdLet, ps.GPTEndSector()) + } else if i == (len(ps.partitions) - 1) { + cmdLet = fmt.Sprintf("%s %d", cmdLet, end) + } else { + cmdLet = fmt.Sprintf("%s %d", cmdLet, ps.partitions[i].end) + } + cmd = fmt.Sprintf("%s %s", cmd, cmdLet) + } + } else { + cmd = fmt.Sprintf("/usr/sbin/parted -a none -s %s -- unit s rm %d mkpart %s", part.disk.dev, part.index, part.diskType) + cmd = fmt.Sprintf("%s %d %d", cmd, part.start, part.end) + if part.bootable { + cmd = fmt.Sprintf("%s set %d boot on", cmd, part.index) + } + } + } else { + // gpt + cmd = fmt.Sprintf("/usr/sbin/sgdisk --set-alignment=1 --delete=%d", part.index) + cmd = fmt.Sprintf("%s --new=%d:%d:%d", cmd, part.index, part.start, end) + if len(part.diskType) != 0 { + cmd = fmt.Sprintf("%s --change-name=%d:\"%s\"", cmd, part.index, part.diskType) + } + if len(part.fs) != 0 { + cmd = fmt.Sprintf("%s --typecode=%d:%s", cmd, part.index, ps.FsToTypeCode(part.fs)) + } + cmd = fmt.Sprintf("%s %s", cmd, ps.dev) + } + log.Infof("Resize cmd: %s", cmd) + if err := ps.doResize(part.disk.dev, cmd); err != nil { + return err + } + return ps.partitions[len(ps.partitions)-1].ResizeFs() +} + +func (ps *DiskPartitions) IsSpaceAvailable(sizeMB int64) bool { + start := ps.getNextPartStart() + freeSect := ps.GPTEndSector() - start + if sizeMB <= 0 { + sizeMB = 1 + } + reqSect := sizeMB * ps.MBSectors() + if reqSect > freeSect { + log.Warningf("No space require %d(%d) left %d", reqSect, sizeMB, freeSect) + return false + } + return true +} + +func (ps *DiskPartitions) MakeLabel() error { + label := LABEL_GPT + if ps.sizeMB <= 1024*1024*2 { + label = LABEL_MSDOS + } + return ps.makeLabel(label) +} + +func (ps *DiskPartitions) makeLabel(label string) error { + ps.label = label + cmd := fmt.Sprintf("/usr/sbin/parted -s %s -- mklabel %s", ps.dev, ps.label) + // cmd = ['/usr/sbin/sgdisk', '-og', self.dev] + _, err := ps.Run(cmd) + return err +} + +func (ps *DiskPartitions) getNextPartIndex() int { + max := 0 + for _, part := range ps.partitions { + if max < part.index { + max = part.index + } + } + return max + 1 +} + +func (ps *DiskPartitions) getNextPartStart() int64 { + var start int64 + if len(ps.partitions) == 0 { + start = ps.MBSectors() // 1MB + } else { + var gap int64 = 2 + lastPart := ps.partitions[len(ps.partitions)-1] + start = ((lastPart.end + gap) / ps.MBSectors()) * ps.MBSectors() + if start < lastPart.end+gap { + start += ps.MBSectors() + } + } + return start +} + +func (ps *DiskPartitions) Run(cmd ...string) ([]string, error) { + return ps.tool.Run(cmd...) +} + +func (ps *DiskPartitions) CreatePartition(sizeMB int64, fs string, doformat bool, uuid string) error { + if len(ps.partitions) == 0 { + if err := ps.MakeLabel(); err != nil { + return err + } + } + start := ps.getNextPartStart() + var end int64 + if sizeMB <= 0 { + end = start + (ps.GPTEndSector()-start)/ps.MBSectors()*ps.MBSectors() - 1 + } else { + end = start + sizeMB*ps.MBSectors() - 1 + } + partIdx := ps.getNextPartIndex() + var cmd string + var diskType string + if ps.label == LABEL_MSDOS { + if partIdx < 5 { + diskType = "primary" + } else if partIdx < 9 { + diskType = "logical" + } else { + return fmt.Errorf("Too many partitions on a MSDOS disk") + } + cmd = fmt.Sprintf("/usr/sbin/parted -a none -s %s -- unit s mkpart %s", ps.dev, diskType) + if len(fs) != 0 { + cmd = fmt.Sprintf("%s %s", cmd, fileutils.FsFormatToDiskType(fs)) + } + cmd = fmt.Sprintf("%s %d %d", cmd, start, end) + } else { + cmd = fmt.Sprintf("/usr/sbin/sgdisk --set-alignment=1 --new=%d:%d:%d", partIdx, start, end) + if len(fs) != 0 { + cmd = fmt.Sprintf("%s --typecode=%d:%s", cmd, partIdx, ps.FsToTypeCode(fs)) + } + cmd = fmt.Sprintf("%s %s", cmd, ps.dev) + } + _, err := ps.Run(cmd) + if err != nil { + return err + } + if err := ps.RetrievePartitionInfo(); err != nil { + return fmt.Errorf("Fail to RetrievePartitionInfo: %v", err) + } + if fs != "" && doformat { + err = ps.partitions[len(ps.partitions)-1].Format(fs, uuid) + if err != nil { + return fmt.Errorf("Fail to format partition: %v", err) + } + if err := ps.RetrievePartitionInfo(); err != nil { + return fmt.Errorf("Fail to RetrievePartitionInfo: %v", err) + } + } + return nil +} + +type IPartitionRunner interface { + Run(cmds ...string) ([]string, error) +} + +type PartitionTool struct { + disks []*DiskPartitions + diskTable map[string][]*DiskPartitions + runner IPartitionRunner +} + +func NewPartitionTool(runner IPartitionRunner) *PartitionTool { + return &PartitionTool{ + disks: make([]*DiskPartitions, 0), + diskTable: make(map[string][]*DiskPartitions), + runner: runner, + } +} + +func (tool *PartitionTool) DebugString() string { + ret := []string{} + disksString := func(disks []*DiskPartitions) []string { + for _, disk := range disks { + ret = append(ret, disk.DebugString()) + } + return ret + } + for driver, disks := range tool.diskTable { + s := fmt.Sprintf("%s: %v", driver, disksString(disks)) + ret = append(ret, s) + } + return strings.Join(ret, "\n") +} + +func (tool *PartitionTool) parseLsDisk(lines []string, driver string) { + disks := sysutils.ParseDiskInfo(lines, driver) + if len(disks) == 0 { + return + } + minCnt := int(math.Min(float64(len(disks)), float64(len(tool.diskTable[driver])))) + for i := 0; i < minCnt; i++ { + tool.diskTable[driver][i].SetInfo(disks[i]) + } +} + +func (tool *PartitionTool) FetchDiskConfs(diskConfs []baremetal.DiskConfiguration) *PartitionTool { + for _, d := range diskConfs { + disk := newDiskPartitions(d.Driver, d.Adapter, d.Size, d.Block, tool) + tool.disks = append(tool.disks, disk) + var key string + if d.Driver == baremetal.DISK_DRIVER_LINUX { + key = NONRAID_DRIVER + } else if d.Driver == baremetal.DISK_DRIVER_PCIE { + key = PCIE_DRIVER + } else { + key = RAID_DRVIER + } + if _, ok := tool.diskTable[key]; !ok { + tool.diskTable[key] = make([]*DiskPartitions, 0) + } + tool.diskTable[key] = append(tool.diskTable[key], disk) + } + return tool +} + +func (tool *PartitionTool) IsAllDisksReady() bool { + for _, d := range tool.disks { + if !d.IsReady() { + log.Errorf("disk %#v not ready", d) + return false + } + } + return true +} + +func (tool *PartitionTool) RetrieveDiskInfo() error { + for _, driver := range []string{RAID_DRVIER, NONRAID_DRIVER, PCIE_DRIVER} { + cmd := fmt.Sprintf("/lib/mos/lsdisk --%s", driver) + ret, err := tool.Run(cmd) + if err != nil { + return err + } + tool.parseLsDisk(ret, driver) + } + return nil +} + +func (tool *PartitionTool) RetrievePartitionInfo() error { + for _, disk := range tool.disks { + if err := disk.RetrievePartitionInfo(); err != nil { + return err + } + } + return nil +} + +func (tool *PartitionTool) ResizePartition(diskIdx int, sizeMB int64) error { + if diskIdx >= 0 && diskIdx < len(tool.disks) { + return tool.disks[diskIdx].ResizePartition(sizeMB) + } + return fmt.Errorf("Invalid disk index: %d", diskIdx) +} + +func (tool *PartitionTool) GetDisks() []*DiskPartitions { + return tool.disks +} + +func (tool *PartitionTool) GetRootDisk() *DiskPartitions { + if len(tool.disks) == 0 { + return nil + } + return tool.disks[0] +} + +func (tool *PartitionTool) GetPCIEDisks() []*DiskPartitions { + disks := make([]*DiskPartitions, 0) + for _, disk := range tool.disks { + if disk.driver == PCIE_DRIVER { + disks = append(disks, disk) + } + } + return disks +} + +func (tool *PartitionTool) CreatePartition(diskIdx int, sizeMB int64, fs string, doformat bool, driver string, uuid string) error { + disks := tool.disks + if driver == PCIE_DRIVER { + disks = tool.GetPCIEDisks() + } + if diskIdx < 0 || diskIdx >= len(disks) { + for _, disk := range disks { + if disk.IsSpaceAvailable(sizeMB) { + return disk.CreatePartition(sizeMB, fs, doformat, uuid) + } + } + } else { + disk := disks[diskIdx] + if disk.IsSpaceAvailable(sizeMB) { + return disk.CreatePartition(sizeMB, fs, doformat, uuid) + } + } + return nil +} + +func (tool *PartitionTool) GetPartitions() []*Partition { + parts := make([]*Partition, 0) + for _, d := range tool.disks { + parts = append(parts, d.partitions...) + } + return parts +} + +func (tool *PartitionTool) Run(cmds ...string) ([]string, error) { + return tool.runner.Run(cmds...) +} + +type SSHPartitionTool struct { + *PartitionTool + term *ssh.Client +} + +func NewSSHPartitionTool(term *ssh.Client) *SSHPartitionTool { + tool := &SSHPartitionTool{ + term: term, + } + tool.PartitionTool = NewPartitionTool(tool) + return tool +} + +func (tool *SSHPartitionTool) Run(cmds ...string) ([]string, error) { + return tool.term.Run(cmds...) +} diff --git a/pkg/baremetal/utils/disktool/disktool_test.go b/pkg/baremetal/utils/disktool/disktool_test.go new file mode 100644 index 0000000000..3d7a2fcc76 --- /dev/null +++ b/pkg/baremetal/utils/disktool/disktool_test.go @@ -0,0 +1,50 @@ +package disktool + +import ( + "testing" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/pkg/util/stringutils" +) + +var ( + term *ssh.Client +) + +func init() { + var err error + term, err = ssh.NewClient("192.168.0.254", 22, "root", "rMw2qrm6Lb5NVpe0", "") + if err != nil { + log.Fatalf("Failed to init ssh client: %v", err) + } +} + +func TestSSHCreate(t *testing.T) { + tool := NewSSHPartitionTool(term) + err := tool.FetchDiskConfs([]baremetal.DiskConfiguration{ + { + Adapter: 0, + Driver: baremetal.DISK_DRIVER_LINUX, + }, + }).RetrieveDiskInfo() + if err != nil { + t.Errorf("Failed to RetrieveDiskInfo: %v", err) + } + err = tool.RetrievePartitionInfo() + if err != nil { + t.Errorf("Failed to RetrievePartitionInfo: %v", err) + } + log.Infof("%s", tool.DebugString()) + + uuid := stringutils.UUID4 + tool.CreatePartition(-1, 32, "swap", true, baremetal.DISK_DRIVER_LINUX, uuid()) + tool.CreatePartition(-1, 1024, "ext4", true, baremetal.DISK_DRIVER_LINUX, uuid()) + err = tool.CreatePartition(-1, -1, "xfs", true, baremetal.DISK_DRIVER_LINUX, uuid()) + //err = tool.ResizePartition(0, 110*1024) + if err != nil { + t.Errorf("Failed to resize fs: %v", err) + } +} diff --git a/pkg/baremetal/utils/disktool/doc.go b/pkg/baremetal/utils/disktool/doc.go new file mode 100644 index 0000000000..dcbd2f4f20 --- /dev/null +++ b/pkg/baremetal/utils/disktool/doc.go @@ -0,0 +1 @@ +package disktool // import "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" diff --git a/pkg/baremetal/utils/ipmitool/doc.go b/pkg/baremetal/utils/ipmitool/doc.go new file mode 100644 index 0000000000..6cc2666658 --- /dev/null +++ b/pkg/baremetal/utils/ipmitool/doc.go @@ -0,0 +1 @@ +package ipmitool // import "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" diff --git a/pkg/baremetal/utils/ipmitool/ipmitool.go b/pkg/baremetal/utils/ipmitool/ipmitool.go new file mode 100644 index 0000000000..7080a02896 --- /dev/null +++ b/pkg/baremetal/utils/ipmitool/ipmitool.go @@ -0,0 +1,628 @@ +package ipmitool + +import ( + "fmt" + "net" + "strconv" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/stringutils" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/baremetal/profiles" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/onecloud/pkg/util/stringutils2" + stage_stringutils "yunion.io/x/onecloud/pkg/util/stringutils2" + "yunion.io/x/onecloud/pkg/util/sysutils" +) + +type IPMIParser struct{} + +func (parser *IPMIParser) GetDefaultTimeout() time.Duration { + return 20 * time.Second +} + +var ( + BOOTDEVS = []string{"pxe", "disk", "safe", "diag", "cdrom", "bios"} + SOLOPTS = []string{"default", "skip", "enable"} +) + +type Args []string + +func newArgs(args ...interface{}) Args { + ret := make([]string, len(args)) + for i, arg := range args { + ret[i] = fmt.Sprintf("%v", arg) + } + return ret +} + +type IPMIExecutor interface { + GetMode() string + ExecuteCommand(args ...string) ([]string, error) +} + +type SSHIPMI struct { + IPMIParser + sshClient *ssh.Client +} + +func NewSSHIPMI(cli *ssh.Client) *SSHIPMI { + return &SSHIPMI{ + sshClient: cli, + } +} + +func (ipmi *SSHIPMI) GetMode() string { + return "ssh" +} + +func (ipmi *SSHIPMI) GetCommand(args ...string) *procutils.Command { + nArgs := []string{"-I", "open"} + nArgs = append(nArgs, args...) + return procutils.NewCommand("/usr/bin/ipmitool", nArgs...) +} + +func (ipmi *SSHIPMI) ExecuteCommand(args ...string) ([]string, error) { + cmd := ipmi.GetCommand(args...) + log.Debugf("[SSHIPMI] execute command: %s", cmd) + return ipmi.sshClient.Run(cmd.String()) +} + +type LanPlusIPMI struct { + IPMIParser + host string + user string + password string + port int +} + +func NewLanPlusIPMI(host, user, password string) *LanPlusIPMI { + return NewLanPlusIPMIWithPort(host, user, password, 623) +} + +func NewLanPlusIPMIWithPort(host, user, password string, port int) *LanPlusIPMI { + return &LanPlusIPMI{ + host: host, + user: user, + password: password, + port: port, + } +} + +func (ipmi *LanPlusIPMI) GetMode() string { + return "rmcp" +} + +func (ipmi *LanPlusIPMI) GetCommand(args ...string) *procutils.Command { + nArgs := []string{ + "--signal=KILL", + fmt.Sprintf("%s", ipmi.GetDefaultTimeout()), + "ipmitool", "-I", "lanplus", "-H", ipmi.host, + "-p", fmt.Sprintf("%d", ipmi.port), + "-U", ipmi.user, + "-P", ipmi.password, + } + nArgs = append(nArgs, args...) + return procutils.NewCommand("timeout", nArgs...) +} + +func (ipmi *LanPlusIPMI) ExecuteCommand(args ...string) ([]string, error) { + cmd := ipmi.GetCommand(args...) + log.Debugf("[LanPlusIPMI] execute command: %s", cmd.String()) + out, err := cmd.Run() + if err != nil { + return nil, err + } + return ssh.ParseOutput(out), nil +} + +func GetSysInfo(exector IPMIExecutor) (*types.SIPMISystemInfo, error) { + // TODO: do cache + args := []string{"fru", "print", "0"} + lines, err := exector.ExecuteCommand(args...) + if err != nil { + return nil, err + } + ret := make(map[string]string) + + keys := map[string]string{ + "manufacture": "Product Manufacturer", + "model": "Product Name", + "bmodel": "Board Product", + "version": "Product Version", + "sn": "Product Serial", + "bsn": "Board Serial", + } + + for _, line := range lines { + key, val := stringutils.SplitKeyValue(line) + if key != "" { + for n, v := range keys { + if _, ok := ret[n]; v == key && !ok { + ret[n] = val + } + } + } + } + _, snOk := ret["sn"] + bsn, bsnOk := ret["bsn"] + if !snOk && bsnOk { + // no product serial + ret["sn"] = bsn + } + info := types.SIPMISystemInfo{} + err = sysutils.DumpMapToObject(ret, &info) + return &info, err +} + +func GetLanChannels(sysinfo *types.SIPMISystemInfo) []int { + return profiles.GetLanChannel(sysinfo) +} + +func GetDefaultLanChannel(sysinfo *types.SIPMISystemInfo) int { + return GetLanChannels(sysinfo)[0] +} + +func GetRootId(sysinfo *types.SIPMISystemInfo) int { + return profiles.GetRootId(sysinfo) +} + +func GetLanConfig(exector IPMIExecutor, channel int) (*types.SIPMILanConfig, error) { + args := newArgs("lan", "print", channel) + lines, err := ExecuteCommands(exector, args) + if err != nil { + return nil, err + } + ret := new(types.SIPMILanConfig) + for _, line := range lines { + key, val := stringutils.SplitKeyValue(line) + if key == "" { + continue + } + switch key { + case "IP Address Source": + if val == "Static Address" { + ret.IPSrc = "static" + } + case "IP Address": + ret.IPAddr = val + case "Subnet Mask": + ret.Netmask = val + case "MAC Address": + ret.Mac, _ = net.ParseMAC(val) + case "Default Gateway IP": + ret.Gateway = val + } + } + return ret, nil +} + +func tryExecuteCommand(exector IPMIExecutor, args ...string) ([]string, error) { + var err error + var ret []string + maxTries := 3 + for tried := 0; tried < maxTries; tried++ { + ret, err = exector.ExecuteCommand(args...) + if err == nil { + return ret, nil + } + sleepTime := time.Second * (1 << uint(tried)) + log.Errorf("Execute args %v error: %v, sleep %s then try again", args, err, sleepTime) + time.Sleep(sleepTime) + } + return ret, err +} + +func ExecuteCommands(exector IPMIExecutor, args ...Args) ([]string, error) { + results := make([]string, 0) + for _, arg := range args { + ret, err := tryExecuteCommand(exector, arg...) + if err != nil { + return nil, err + } + results = append(results, ret...) + } + return results, nil +} + +func doActions(exector IPMIExecutor, actionName string, args ...Args) error { + _, err := ExecuteCommands(exector, args...) + if err != nil { + return fmt.Errorf("Do %s action error: %v", actionName, err) + } + return nil +} + +func SetLanDHCP(exector IPMIExecutor, lanChannel int) error { + args := newArgs("lan", "set", lanChannel, "ipsrc", "dhcp") + return doActions(exector, "set_lan_dhcp", args) +} + +func SetLanStatic( + exector IPMIExecutor, + channel int, + ip string, + mask string, + gateway string, +) error { + config, err := GetLanConfig(exector, channel) + if err != nil { + return err + } + var argss []Args + if config.IPAddr == ip && config.Netmask == mask && config.Gateway == gateway { + argss = []Args{ + newArgs("lan", "set", channel, "ipsrc", "static"), + newArgs("lan", "set", channel, "ipaddr", ip), + newArgs("lan", "set", channel, "netmask", mask), + newArgs("lan", "set", channel, "defgw", "ipaddr", gateway), + } + } else { + argss = []Args{ + newArgs("lan", "set", channel, "ipaddr", ip), + newArgs("lan", "set", channel, "defgw", "ipaddr", gateway), + newArgs("lan", "set", channel, "netmask", mask), + newArgs("lan", "set", channel, "ipsrc", "static"), + } + } + return doActions(exector, "set_lan_static", argss...) +} + +func SetLanStaticIP(exector IPMIExecutor, channel int, ip string) error { + args := newArgs("lan", "set", channel, "ipaddr", ip) + return doActions(exector, "set_lan_static_ip", args) +} + +func setLanAccess(exector IPMIExecutor, channel int, access string) error { + args := []Args{ + newArgs("lan", "set", channel, "access", access), + // newArgs("lan", "set", channel, "auth", "ADMIN", "MD5"), + } + return doActions(exector, "set_lan_access", args...) +} + +func EnableLanAccess(exector IPMIExecutor, channel int) error { + return setLanAccess(exector, channel, "on") +} + +func SetLanUserPasswd(exector IPMIExecutor, channel int, rootId int, user string, password string) error { + var err error + password, err = stage_stringutils.EscapeEchoString(password) + if err != nil { + return fmt.Errorf("EscapeEchoString for password: %s, error: %v", password, err) + } + args := []Args{ + newArgs("user", "enable", rootId), + newArgs("user", "set", "name", rootId, user), + newArgs("user", "set", "password", rootId, fmt.Sprintf("\"%s\"", password)), + newArgs("user", "priv", rootId, 4, channel), + } + err = doActions(exector, "set_lan_user_password", args...) + if err != nil { + return err + } + args = []Args{newArgs( + "raw", "0x06", "0x43", + fmt.Sprintf("0x%02x", 0xb0+channel), + fmt.Sprintf("0x%02x", rootId), "0x04", "0x00")} + err = doActions(exector, "set_lan_user_password2", args...) + if err == nil { + return nil + } + args = []Args{newArgs( + "channel", "setaccess", channel, + rootId, "link=on", "ipmi=on", + "callin=on", "privilege=4", + )} + return doActions(exector, "set_lan_user_password3", args...) +} + +func SetLanPasswd(exector IPMIExecutor, rootId int, password string) error { + password, err := stringutils2.EscapeEchoString(password) + if err != nil { + return fmt.Errorf("EscapeEchoString for password: %v", err) + } + args := newArgs("user", "set", "password", rootId, fmt.Sprint("\"%s\"", password)) + return doActions(exector, "set_lan_passwd", args) +} + +func GetChassisPowerStatus(exector IPMIExecutor) (string, error) { + args := newArgs("chassis", "power", "status") + ret, err := ExecuteCommands(exector, args) + if err != nil { + return "", err + } + for _, line := range ret { + if strings.Contains(line, "Chassis Power is") { + data := strings.Split(line, " ") + status := strings.ToLower(strings.TrimSpace(data[len(data)-1])) + return status, nil + } + } + return "", fmt.Errorf("Unknown chassis status") +} + +func GetBootFlags(exector IPMIExecutor) (*types.SIPMIBootFlags, error) { + args := newArgs("raw", "0x00", "0x09", "0x05", "0x00", "0x00") + ret, err := ExecuteCommands(exector, args) + if err != nil { + return nil, err + } + bytes, err := HexStr2Bytes(ret[0]) + if err != nil { + return nil, err + } + bootdevIdx := ((bytes[3] >> 2) & 0x0f) - 1 + bootdev := "" + if bootdevIdx >= 0 && int(bootdevIdx) < len(BOOTDEVS) { + bootdev = BOOTDEVS[bootdevIdx] + } + flags := &types.SIPMIBootFlags{ + Dev: bootdev, + } + solIdx := (bytes[4] & 0x03) + if solIdx == 1 { + sol := false + flags.Sol = &sol + } else if solIdx == 2 { + sol := true + flags.Sol = &sol + } + return flags, nil +} + +func HexStr2Bytes(hs string) ([]int64, error) { + b := []int64{} + for _, x := range strings.Split(hs, " ") { + intV, err := strconv.ParseInt(x, 16, 64) + if err != nil { + return nil, err + } + b = append(b, intV) + } + return b, nil +} + +func GetACPIPowerStatus(exector IPMIExecutor) ([]int64, error) { + args := newArgs("raw", "0x06", "0x07") + ret, err := ExecuteCommands(exector, args) + if err != nil { + return nil, err + } + return HexStr2Bytes(ret[0]) +} + +func DoSoftShutdown(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "soft") + return doActions(exector, "do_soft_shutdown", args) +} + +func DoHardShutdown(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "off") + return doActions(exector, "do_hard_shutdown", args) +} + +func DoPowerOn(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "on") + return doActions(exector, "do_power_on", args) +} + +func DoPowerReset(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "reset") + return doActions(exector, "do_power_reset", args) +} + +func DoPowerCycle(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "cycle") + return doActions(exector, "do_power_cycle", args) +} + +func DoReboot(exector IPMIExecutor) error { + maxTries := 10 + + var status string + var err error + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + + isValidStatus := func(s string) bool { + return utils.IsInStringArray(s, []string{types.POWER_STATUS_ON, types.POWER_STATUS_OFF}) + } + + for tried := 0; !isValidStatus(status) && tried <= maxTries; tried++ { + time.Sleep(1 * time.Second) + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + } + + if !isValidStatus(status) { + return fmt.Errorf("Unexpected status: %s", status) + } + + // do shutdown + if status == types.POWER_STATUS_ON { + err = DoHardShutdown(exector) + if err != nil { + return err + } + time.Sleep(1 * time.Second) + for tried := 0; tried < maxTries; tried++ { + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + if status == types.POWER_STATUS_OFF { + break + } + time.Sleep(1 * time.Second) + } + } + + // do power on + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + for tried := 0; status != types.POWER_STATUS_ON && tried < maxTries; tried++ { + err = DoPowerOn(exector) + if err != nil { + return err + } + time.Sleep(1 * time.Second) + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + } + + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + if status != types.POWER_STATUS_ON { + return fmt.Errorf("do reboot fail to poweron, current status: %s", status) + } + return nil +} + +func doRebootToFlag(exector IPMIExecutor, setFunc func(IPMIExecutor) error) error { + err := setFunc(exector) + if err != nil { + return err + } + return DoReboot(exector) +} + +func SetRebootToDisk(exector IPMIExecutor) error { + return SetBootFlags(exector, "disk", tristate.True, true) +} + +func DoRebootToDisk(exector IPMIExecutor) error { + return doRebootToFlag(exector, SetRebootToDisk) +} + +func SetRebootToPXE(exector IPMIExecutor) error { + return SetBootFlagPXE(exector) +} + +func DoRebootToPXE(exector IPMIExecutor) error { + return doRebootToFlag(exector, SetRebootToPXE) +} + +func SetRebootToBIOS(exector IPMIExecutor) error { + return SetBootFlags(exector, "bios", tristate.True, false) +} + +func DoRebootToBIOS(exector IPMIExecutor) error { + return doRebootToFlag(exector, SetRebootToBIOS) +} + +func SetBootFlagPXE(exector IPMIExecutor) error { + return setBootFlagsV2(exector, "pxe") +} + +func SetBootFlags( + exector IPMIExecutor, + bootdev string, + sol tristate.TriState, + enablePersistent bool, +) error { + err := setBootFlagsV1(exector, bootdev, sol, enablePersistent) + if err == nil { + return nil + } + return setBootFlagsV2(exector, bootdev) +} + +func setBootFlagsV1( + exector IPMIExecutor, + bootdev string, + sol tristate.TriState, + enablePersistent bool, +) error { + cmd := []interface{}{"raw", "0x00", "0x08", "0x05"} + bootdevIdx := 0 + if ok, idx := utils.InStringArray(bootdev, BOOTDEVS); ok { + bootdevIdx = idx + 1 + } else { + return fmt.Errorf("Illegal bootdev %s", bootdev) + } + valid := 0x80 + if enablePersistent { + valid = valid + 0x40 + } + solIdx := 0 + if !sol.IsNone() { + if sol.IsTrue() { + solIdx = 2 + } else { + solIdx = 1 + } + } + for _, x := range []int{valid, bootdevIdx << 2, solIdx, 0, 0} { + cmd = append(cmd, fmt.Sprintf("0x%02x", x)) + } + return doActions(exector, "set_boot_flags_v1", newArgs(cmd...)) +} + +func setBootFlagsV2(exector IPMIExecutor, bootdev string) error { + return doActions( + exector, + fmt.Sprintf("set_boot_flag_%s", bootdev), + newArgs("chassis", "bootdev", bootdev), + ) +} + +func GetIPMILanPort(exector IPMIExecutor) (string, error) { + ret, err := ExecuteCommands(exector, newArgs("delloem", "lan", "get")) + if err != nil { + return "", err + } + return ret[1], nil +} + +func SetDellIPMILanPortShared(exector IPMIExecutor) error { + args1 := newArgs("delloem", "lan", "set", "shared") + args2 := newArgs("delloem", "lan", "set", "shared", "with", "lom1") + err2 := doActions(exector, "_dell_set_ipmi_lan_port_shared_02", args2) + if err2 != nil { + return doActions(exector, "_dell_set_ipmi_lan_port_shared_01", args1) + } + return nil +} + +func SetHuaweiIPMILanPortShared(exector IPMIExecutor) error { + args := []Args{ + newArgs( + "raw", "0xc", "0x1", "0x1", "0xd7", "0xdb", + "0x07", "0x00", "0x2", + ), + newArgs( + "raw", "0x30", "0x93", "0xdb", "0x07", "0x00", + "0x05", "0x0d", "0x0", "0x0", "0x1", "0x0", + ), + } + return doActions(exector, "_huawei_set_ipmi_lan_port_shared", args...) +} + +func SetIPMILanPortDedicated(exector IPMIExecutor) error { + return doActions( + exector, + "set_ipmi_lan_port_dedicated", + newArgs("delloem", "lan", "set", "dedicated"), + ) +} + +func DoBMCReset(exector IPMIExecutor) error { + return doActions(exector, "do_bmc_reset", newArgs("mc", "reset", "cold")) +} diff --git a/pkg/baremetal/utils/ipmitool/ipmitool_test.go b/pkg/baremetal/utils/ipmitool/ipmitool_test.go new file mode 100644 index 0000000000..2a05ffefa4 --- /dev/null +++ b/pkg/baremetal/utils/ipmitool/ipmitool_test.go @@ -0,0 +1,32 @@ +package ipmitool + +import ( + "reflect" + "testing" +) + +func TestGetSysInfo(t *testing.T) { + type args struct { + exector IPMIExecutor + } + tests := []struct { + name string + args args + want *SystemInfo + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetSysInfo(tt.args.exector) + if (err != nil) != tt.wantErr { + t.Errorf("GetSysInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSysInfo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/baremetal/utils/raid/doc.go b/pkg/baremetal/utils/raid/doc.go new file mode 100644 index 0000000000..1b3d071b6f --- /dev/null +++ b/pkg/baremetal/utils/raid/doc.go @@ -0,0 +1 @@ +package raid // import "yunion.io/x/onecloud/pkg/baremetal/utils/raid" diff --git a/pkg/baremetal/utils/raid/drivers/doc.go b/pkg/baremetal/utils/raid/drivers/doc.go new file mode 100644 index 0000000000..800773ba9e --- /dev/null +++ b/pkg/baremetal/utils/raid/drivers/doc.go @@ -0,0 +1 @@ +package drivers // import "yunion.io/x/onecloud/pkg/baremetal/utils/raid/drivers" diff --git a/pkg/baremetal/utils/raid/drivers/drivers.go b/pkg/baremetal/utils/raid/drivers/drivers.go new file mode 100644 index 0000000000..d46d8941ee --- /dev/null +++ b/pkg/baremetal/utils/raid/drivers/drivers.go @@ -0,0 +1,99 @@ +package drivers + +import ( + "fmt" + + _ "yunion.io/x/onecloud/pkg/baremetal/utils/raid/hpssactl" + _ "yunion.io/x/onecloud/pkg/baremetal/utils/raid/megactl" + _ "yunion.io/x/onecloud/pkg/baremetal/utils/raid/mvcli" + _ "yunion.io/x/onecloud/pkg/baremetal/utils/raid/sas2iru" + + "yunion.io/x/onecloud/pkg/baremetal/utils/raid" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +func GetDriver(name string, term *ssh.Client) raid.IRaidDriver { + factory := raid.RaidDrivers[name] + if factory == nil { + return nil + } + return factory(term) +} + +func GetDrivers(term *ssh.Client) []raid.IRaidDriver { + ret := []raid.IRaidDriver{} + for _, factory := range raid.RaidDrivers { + ret = append(ret, factory(term)) + } + return ret +} + +func BuildRaid(driver raid.IRaidDriver, confs []*baremetal.BaremetalDiskConfig, adapterIdx int) error { + if err := driver.PreBuildRaid(confs, adapterIdx); err != nil { + return fmt.Errorf("PreBuildRaid: %v", err) + } + var adapter raid.IRaidAdapter + for _, tmp := range driver.GetAdapters() { + if tmp.GetIndex() == adapterIdx { + adapter = tmp + break + } + } + if adapter == nil { + return fmt.Errorf("Not found adapter by index %d", adapterIdx) + } + if err := buildRaid(adapter, confs); err != nil { + return fmt.Errorf("Driver %s, adapter %d build raid: %v", driver.GetName(), adapterIdx, err) + } + return nil +} + +func buildRaid(adapter raid.IRaidAdapter, confs []*baremetal.BaremetalDiskConfig) error { + if err := adapter.PreBuildRaid(confs); err != nil { + return fmt.Errorf("PreBuildRaid: %v", err) + } + if err := adapter.RemoveLogicVolumes(); err != nil { + return fmt.Errorf("RemoveLogicVolumes: %v", err) + } + devs := adapter.GetDevices() + if len(devs) == 0 { + // no disk to build + return nil + } + + var selected []*baremetal.BaremetalStorage + var nonDisks []*baremetal.BaremetalStorage + left := devs + + for _, conf := range confs { + selected, left = baremetal.RetrieveStorages(conf, left) + if len(selected) == 0 { + return fmt.Errorf("No enough disks for config %#v", conf) + } + var err error + switch conf.Conf { + case baremetal.DISK_CONF_RAID5: + err = adapter.BuildRaid5(selected, conf) + case baremetal.DISK_CONF_RAID10: + err = adapter.BuildRaid10(selected, conf) + case baremetal.DISK_CONF_NONE: + nonDisks = append(nonDisks, selected...) + case baremetal.DISK_CONF_RAID0: + err = adapter.BuildRaid0(selected, conf) + case baremetal.DISK_CONF_RAID1: + err = adapter.BuildRaid1(selected, conf) + default: + return fmt.Errorf("Unknown raid config %s", conf.Conf) + } + if err != nil { + return fmt.Errorf("Build raid %s: %v", conf.Conf, err) + } + } + if len(nonDisks) > 0 { + if err := adapter.BuildNoneRaid(nonDisks); err != nil { + return fmt.Errorf("Build raw disks: %v", err) + } + } + return nil +} diff --git a/pkg/baremetal/utils/raid/hpssactl/doc.go b/pkg/baremetal/utils/raid/hpssactl/doc.go new file mode 100644 index 0000000000..34d932cbfd --- /dev/null +++ b/pkg/baremetal/utils/raid/hpssactl/doc.go @@ -0,0 +1 @@ +package hpssactl // import "yunion.io/x/onecloud/pkg/baremetal/utils/raid/hpssactl" diff --git a/pkg/baremetal/utils/raid/hpssactl/hpssactl.go b/pkg/baremetal/utils/raid/hpssactl/hpssactl.go new file mode 100644 index 0000000000..c2cfaa9587 --- /dev/null +++ b/pkg/baremetal/utils/raid/hpssactl/hpssactl.go @@ -0,0 +1,385 @@ +package hpssactl + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/util/stringutils" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/raid" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/regutils2" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type HPSARaidPhyDev struct { + *raid.RaidBasePhyDev + addr string +} + +func newHPSARaidPhyDev(addr string, adapter int, rotate bool) *HPSARaidPhyDev { + b := raid.NewRaidBasePhyDev(baremetal.DISK_DRIVER_HPSARAID) + b.Adapter = adapter + return &HPSARaidPhyDev{ + RaidBasePhyDev: b, + addr: addr, + } +} + +func (dev *HPSARaidPhyDev) ToBaremetalStorage() *baremetal.BaremetalStorage { + s := dev.RaidBasePhyDev.ToBaremetalStorage() + s.Addr = dev.addr + + return s +} + +func (dev *HPSARaidPhyDev) parseLine(line string) bool { + key, val := stringutils.SplitKeyValue(line) + if key == "" { + return false + } + switch key { + case "Size": + dat := strings.Split(val, " ") + szStr, unitStr := dat[0], dat[1] + var sz int64 + szF, err := strconv.ParseFloat(szStr, 64) + if err != nil { + log.Errorf("Parse size string %s: %v", szStr, err) + return false + } + switch unitStr { + case "GB": + sz = int64(szF * 1000 * 1000 * 1000) + case "TB": + sz = int64(szF * 1000 * 1000 * 1000 * 1000) + case "MB": + sz = int64(szF * 1000 * 1000) + default: + log.Errorf("Unsupported unit: %s", unitStr) + return false + } + dev.Size = sz / 1024 / 1024 + case "Model": + dev.Model = strings.Join(regexp.MustCompile(`\s+`).Split(val, -1), " ") + case "Status": + dev.Status = val + default: + return false + } + return true +} + +func (dev *HPSARaidPhyDev) isComplete() bool { + if !dev.RaidBasePhyDev.IsComplete() { + return false + } + if dev.Size < 0 { + return false + } + return true +} + +func GetSpecString(dev *baremetal.BaremetalStorage) string { + return dev.Addr +} + +type HPSARaidAdaptor struct { + index int + raid *HPSARaid + devs []*HPSARaidPhyDev +} + +func newHPSARaidAdaptor(index int, raid *HPSARaid) *HPSARaidAdaptor { + return &HPSARaidAdaptor{ + index: index, + raid: raid, + } +} + +func (adapter *HPSARaidAdaptor) GetIndex() int { + return adapter.index +} + +func (adapter *HPSARaidAdaptor) PreBuildRaid(confs []*baremetal.BaremetalDiskConfig) error { + return nil +} + +func (adapter *HPSARaidAdaptor) ParsePhyDevs() error { + parseByCmd := func(cmd string, isRotate bool) error { + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return err + } + adapter.parsePhyDevs(ret, isRotate) + return nil + } + cmd1 := GetCommand("controller", "slot=%d", fmt.Sprintf("%d", adapter.index), "ssdphysicaldrive", "all", "show", "detail") + cmd2 := GetCommand("controller", "slot=%d", fmt.Sprintf("%d", adapter.index), "physicaldrive", "all", "show", "detail") + var err error + if err = parseByCmd(cmd1, false); err != nil { + log.Errorf("parsePhyDevs by cmd %q: %v", cmd1, err) + } + if err = parseByCmd(cmd2, true); err != nil { + log.Errorf("parsePhyDevs by cmd %q: %v", cmd1, err) + } + return err +} + +func (adapter *HPSARaidAdaptor) parsePhyDevs(lines []string, isRotate bool) { + var phydev *HPSARaidPhyDev + for _, line := range lines { + m := regutils2.SubGroupMatch(`physicaldrive\s+(?P\w+:\w+:\w)`, line) + if len(m) != 0 { + phydev = newHPSARaidPhyDev(m["addr"], adapter.index, isRotate) + } else if phydev != nil && phydev.parseLine(line) && phydev.isComplete() { + oldDev := adapter.getPhyDevByAddr(phydev.addr) + if oldDev != nil { + adapter.devs = append(adapter.devs, phydev) + } + phydev = nil + } + } +} + +func (adapter *HPSARaidAdaptor) getPhyDevByAddr(addr string) *HPSARaidPhyDev { + for _, dev := range adapter.devs { + if addr == dev.addr { + return dev + } + } + return nil +} + +func (adapter *HPSARaidAdaptor) GetDevices() []*baremetal.BaremetalStorage { + ret := []*baremetal.BaremetalStorage{} + for _, dev := range adapter.devs { + ret = append(ret, dev.ToBaremetalStorage()) + } + return ret +} + +func (adapter *HPSARaidAdaptor) conf2Params(conf *baremetal.BaremetalDiskConfig) []string { + params := []string{} + if conf.Direct != nil { + if *(conf.Direct) { + params = append(params, "caching=disable") + } else { + params = append(params, "caching=enable") + } + } + if conf.Strip != nil { + params = append(params, fmt.Sprintf("stripsize=%d", *(conf.Strip))) + } + return params +} + +func (adapter *HPSARaidAdaptor) getLastArray() (string, error) { + cmd := GetCommand("controller", fmt.Sprintf("slot=%d", adapter.index), "logicaldrive", "all", "show") + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return "", err + } + var lastArray string + for _, line := range ret { + m := regutils2.SubGroupMatch(`array\s+(?P\w+)`, line) + if len(m) > 0 { + lastArray = m["idx"] + return lastArray, nil + } + } + return "", nil +} + +func (adapter *HPSARaidAdaptor) buildRaid(level string, devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + labels := []string{} + for _, dev := range devs { + labels = append(labels, fmt.Sprintf("%s", GetSpecString(dev))) + } + args := []string{ + "controller", fmt.Sprintf("slot=%d", adapter.GetIndex()), + "create", "type=ld", fmt.Sprintf("drives=%s", strings.Join(labels, ",")), + fmt.Sprintf("raid=%s", level), + } + if len(conf.Size) > 0 { + args = append(args, fmt.Sprintf("size=%d", conf.Size[0])) + } + params := adapter.conf2Params(conf) + args = append(args, params...) + cmd := GetCommand(args...) + _, err := adapter.raid.term.Run(cmd) + if err != nil { + return err + } + if len(conf.Size) > 0 { + array, err := adapter.getLastArray() + if err != nil { + return fmt.Errorf("getLastArray: %v", err) + } + cmds := []string{} + for _, sz := range conf.Size[1:] { + args = []string{"controller", fmt.Sprintf("slot=%d", adapter.index), + "array", array, "create", "type=ld", + fmt.Sprintf("raid=%s", level), + fmt.Sprintf("size=%d", sz), + } + args = append(args, params...) + cmds = append(cmds, GetCommand(args...)) + } + _, err = adapter.raid.term.Run(cmds...) + } + return err +} + +func (adapter *HPSARaidAdaptor) BuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("0", devs, conf) +} + +func (adapter *HPSARaidAdaptor) BuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("1", devs, conf) +} + +func (adapter *HPSARaidAdaptor) BuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("5", devs, conf) +} + +func (adapter *HPSARaidAdaptor) BuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("10", devs, conf) +} + +func (adapter *HPSARaidAdaptor) BuildNoneRaid(devs []*baremetal.BaremetalStorage) error { + for _, d := range devs { + // WT|WB] [NORA|RA] [Direct|Cached] [CachedBadBBU|NoCachedBadBBU] + useWT := true + useDirect := true + if err := adapter.buildRaid("0", []*baremetal.BaremetalStorage{d}, &baremetal.BaremetalDiskConfig{WT: &useWT, Direct: &useDirect}); err != nil { + return err + } + } + return nil +} + +func (adapter *HPSARaidAdaptor) removeLogicVolume(idx int) error { + cmd := GetCommand("controller", fmt.Sprintf("slot=%d", adapter.index, "logicaldrive", + fmt.Sprintf("%d", idx), "delete", "forced", + )) + _, err := adapter.raid.term.Run(cmd) + return err +} + +func (adapter *HPSARaidAdaptor) GetLogicVolumes() ([]int, error) { + cmd := GetCommand("controller", fmt.Sprintf("slot=%d", adapter.index), "logicaldrive", "all", "show") + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return nil, err + } + return adapter.parseLogicalVolumes(ret) +} + +func (adapter *HPSARaidAdaptor) parseLogicalVolumes(lines []string) ([]int, error) { + lvs := []int{} + for _, line := range lines { + m := regutils2.SubGroupMatch(`logicaldrive\s+(?P\w+)`, line) + if len(m) > 0 { + idxStr := m["addr"] + idx, err := strconv.Atoi(idxStr) + if err != nil { + return nil, fmt.Errorf("%s not int: %v", idxStr, err) + } + lvs = append(lvs, idx) + } + } + return lvs, nil +} + +func (adapter *HPSARaidAdaptor) RemoveLogicVolumes() error { + lvs, err := adapter.GetLogicVolumes() + if err != nil { + return fmt.Errorf("Failed to get logic volumes: %v", err) + } + for i := len(lvs) - 1; i >= 0; i-- { + if err := adapter.removeLogicVolume(i); err != nil { + return fmt.Errorf("Remove %d logical volume: %v", i, err) + } + } + return nil +} + +type HPSARaid struct { + term *ssh.Client + adapters []*HPSARaidAdaptor +} + +func NewHPSARaid(term *ssh.Client) raid.IRaidDriver { + return &HPSARaid{ + term: term, + adapters: make([]*HPSARaidAdaptor, 0), + } +} + +func (r *HPSARaid) ParsePhyDevs() error { + if !utils.IsInStringArray("hpsa", raid.GetModules(r.term)) { + return fmt.Errorf("Not found hpsa module") + } + cmd := GetCommand("controller", "all", "show") + ret, err := r.term.Run(cmd) + if err != nil { + return err + } + return r.parsePhyDevs(ret) +} + +func (r *HPSARaid) parsePhyDevs(lines []string) error { + for _, line := range lines { + m := regutils2.SubGroupMatch(`\s+Slot\s+(?P[0-9]+)\s+`, line) + if len(m) > 0 { + idxStr := m["idx"] + idx, err := strconv.Atoi(idxStr) + if err != nil { + return err + } + adapter := newHPSARaidAdaptor(idx, r) + r.adapters = append(r.adapters, adapter) + } + } + for _, a := range r.adapters { + if err := a.ParsePhyDevs(); err != nil { + return err + } + } + return nil +} + +func (r *HPSARaid) PreBuildRaid(_ []*baremetal.BaremetalDiskConfig, _ int) error { + return nil +} + +func (r *HPSARaid) GetAdapters() []raid.IRaidAdapter { + ret := make([]raid.IRaidAdapter, 0) + for _, a := range r.adapters { + ret = append(ret, a) + } + return ret +} + +func (r *HPSARaid) GetName() string { + return baremetal.DISK_DRIVER_HPSARAID +} + +func (r *HPSARaid) CleanRaid() error { + // pass + return nil +} + +func GetCommand(args ...string) string { + bin := "/opt/hp/hpssacli/bld/hpssacli" + return raid.GetCommand(bin, args...) +} + +func init() { + raid.RegisterDriver(baremetal.DISK_DRIVER_HPSARAID, NewHPSARaid) +} diff --git a/pkg/baremetal/utils/raid/interface.go b/pkg/baremetal/utils/raid/interface.go new file mode 100644 index 0000000000..a49632c025 --- /dev/null +++ b/pkg/baremetal/utils/raid/interface.go @@ -0,0 +1,28 @@ +package raid + +import ( + "yunion.io/x/onecloud/pkg/compute/baremetal" +) + +type IRaidDriver interface { + ParsePhyDevs() error + GetName() string + GetAdapters() []IRaidAdapter + PreBuildRaid(confs []*baremetal.BaremetalDiskConfig, adapterIdx int) error + + CleanRaid() error +} + +type IRaidAdapter interface { + GetIndex() int + PreBuildRaid(confs []*baremetal.BaremetalDiskConfig) error + GetLogicVolumes() ([]int, error) + RemoveLogicVolumes() error + GetDevices() []*baremetal.BaremetalStorage + + BuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error + BuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error + BuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error + BuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error + BuildNoneRaid(devs []*baremetal.BaremetalStorage) error +} diff --git a/pkg/baremetal/utils/raid/megactl/doc.go b/pkg/baremetal/utils/raid/megactl/doc.go new file mode 100644 index 0000000000..17b80d0876 --- /dev/null +++ b/pkg/baremetal/utils/raid/megactl/doc.go @@ -0,0 +1 @@ +package megactl // import "yunion.io/x/onecloud/pkg/baremetal/utils/raid/megactl" diff --git a/pkg/baremetal/utils/raid/megactl/megactl.go b/pkg/baremetal/utils/raid/megactl/megactl.go new file mode 100644 index 0000000000..31ade2a507 --- /dev/null +++ b/pkg/baremetal/utils/raid/megactl/megactl.go @@ -0,0 +1,775 @@ +package megactl + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/stringutils" + "yunion.io/x/pkg/utils" + + raiddrivers "yunion.io/x/onecloud/pkg/baremetal/utils/raid" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/regutils2" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +var ( + sizePattern = regexp.MustCompile(`(?P0x[0-9a-fA-F]+)`) + adapterPatter = regexp.MustCompile(`^Adapter #(?P[0-9]+)`) +) + +type MegaRaidPhyDev struct { + *raiddrivers.RaidBasePhyDev + + enclosure int + slot int + minStripSize int + maxStripSize int + sector int64 + block int64 +} + +func NewMegaRaidPhyDev() *MegaRaidPhyDev { + return &MegaRaidPhyDev{ + RaidBasePhyDev: raiddrivers.NewRaidBasePhyDev(baremetal.DISK_DRIVER_MEGARAID), + enclosure: -1, + slot: -1, + minStripSize: -1, + maxStripSize: -1, + sector: -1, + block: 512, + } +} + +func (dev *MegaRaidPhyDev) ToBaremetalStorage() *baremetal.BaremetalStorage { + s := dev.RaidBasePhyDev.ToBaremetalStorage() + s.Enclosure = dev.enclosure + s.Slot = dev.slot + s.Size = dev.GetSize() + s.MinStripSize = int64(dev.minStripSize) + s.MaxStripSize = int64(dev.maxStripSize) + s.Block = dev.block + s.Sector = dev.sector + return s +} + +func (dev *MegaRaidPhyDev) GetSize() int64 { + return dev.sector * int64(dev.block) / 1024 / 1024 // MB +} + +func (dev *MegaRaidPhyDev) parseLine(line string) bool { + key, val := stringutils.SplitKeyValue(line) + if key == "" { + return false + } + switch key { + case "Media Type": + if val == "Hard Disk Device" { + dev.Rotate = tristate.True + } else { + dev.Rotate = tristate.False + } + case "Enclosure Device ID": + enclosure, err := strconv.Atoi(val) + if err == nil { + dev.enclosure = enclosure + } + case "Slot Number": + dev.slot, _ = strconv.Atoi(val) + case "Coerced Size": + sizeStr := regutils2.GetParams(sizePattern, val)["sector"] + if len(sizeStr) != 0 { + sizeStr = strings.Replace(sizeStr, "0x", "", -1) + sector, err := strconv.ParseInt(sizeStr, 16, 64) + if err != nil { + log.Errorf("Parse sector %q to Int error: %v", sizeStr, err) + } + dev.sector = sector + } else { + dev.sector = 0 + } + case "Inquiry Data": + dev.Model = strings.Join(regexp.MustCompile(`\s+`).Split(val, -1), " ") + case "Firmware state": + if val == "JBOD" { + dev.Status = "jbod" + } else if strings.Contains(strings.ToLower(val), "online") { + dev.Status = "online" + } else { + dev.Status = "offline" + } + case "Logical Sector Size": + block, err := strconv.Atoi(val) + if err != nil { + log.Errorf("parse logical sector size error: %v", err) + dev.block = 512 + } else { + dev.block = int64(block) + } + default: + return false + } + return true +} + +func (dev *MegaRaidPhyDev) parseStripSize(lines []string) error { + size2Int := func(sizeStr string) int { + sz, _ := strconv.ParseFloat(strings.Fields(sizeStr)[0], 32) + szInt := int(sz) + if strings.Contains(sizeStr, "KB") { + return szInt + } + if strings.Contains(sizeStr, "MB") { + return szInt * 1024 + } + return -1 + } + for _, line := range lines { + if strings.Contains(line, "Min") { + dev.minStripSize = size2Int(strings.Split(line, ": ")[1]) + } + if strings.Contains(line, "Max") { + dev.maxStripSize = size2Int(strings.Split(line, ": ")[1]) + } + } + return nil +} + +func (dev *MegaRaidPhyDev) isComplete() bool { + if !dev.RaidBasePhyDev.IsComplete() { + return false + } + if dev.sector < 0 { + return false + } + if dev.block < 0 { + return false + } + if dev.slot < 0 { + return false + } + return true +} + +func (dev *MegaRaidPhyDev) isJBOD() bool { + return dev.Status == "jbod" +} + +func GetSpecString(dev *baremetal.BaremetalStorage) string { + if dev.Enclosure < 0 { + return fmt.Sprintf(":%d", dev.Slot) + } + return fmt.Sprintf("%d:%d", dev.Enclosure, dev.Slot) +} + +type MegaRaidAdaptor struct { + index int + raid *MegaRaid + devs []*MegaRaidPhyDev +} + +func NewMegaRaidAdaptor(index int, raid *MegaRaid) *MegaRaidAdaptor { + return &MegaRaidAdaptor{ + index: index, + raid: raid, + } +} + +func (adapter *MegaRaidAdaptor) GetIndex() int { + return adapter.index +} + +func (adapter *MegaRaidAdaptor) getTerm() *ssh.Client { + return adapter.raid.term +} + +func (adapter *MegaRaidAdaptor) remoteRun(cmds ...string) ([]string, error) { + return adapter.getTerm().Run(cmds...) +} + +func (adapter *MegaRaidAdaptor) AddPhyDev(dev *MegaRaidPhyDev) { + dev.Adapter = adapter.index + adapter.devs = append(adapter.devs, dev) +} + +func (adapter *MegaRaidAdaptor) GetDevices() []*baremetal.BaremetalStorage { + ret := []*baremetal.BaremetalStorage{} + for _, dev := range adapter.devs { + ret = append(ret, dev.ToBaremetalStorage()) + } + return ret +} + +func (adapter *MegaRaidAdaptor) GetLogicVolumes() ([]int, error) { + cmd := GetCommand("-LDInfo", "-Lall", fmt.Sprintf("-a%d", adapter.index)) + ret, err := adapter.remoteRun(cmd) + if err != nil { + return nil, fmt.Errorf("GetLogicVolumes error: %v", err) + } + return adapter.parseLogicVolumes(ret), nil +} + +func (adapter *MegaRaidAdaptor) parseLogicVolumes(lines []string) []int { + lvIdx := []int{} + for _, line := range lines { + key, val := stringutils.SplitKeyValue(line) + if key != "" && key == "Virtual Drive" { + idx, _ := strconv.Atoi(strings.Split(val, " ")[0]) + lvIdx = append(lvIdx, idx) + } + } + return lvIdx +} + +func (adapter *MegaRaidAdaptor) PreBuildRaid(confs []*baremetal.BaremetalDiskConfig) error { + adapter.clearJBODDisks() + return nil +} + +func (adapter *MegaRaidAdaptor) conf2ParamsStorcliSize(conf *baremetal.BaremetalDiskConfig) []string { + params := []string{} + szStr := []string{} + if len(conf.Size) > 0 { + for _, sz := range conf.Size { + szStr = append(szStr, fmt.Sprintf("%dMB", sz)) + } + params = append(params, fmt.Sprintf("Size=%s", strings.Join(szStr, ","))) + } + return params +} + +func (adapter *MegaRaidAdaptor) conf2ParamsStorcli(conf *baremetal.BaremetalDiskConfig) []string { + params := []string{} + if conf.WT != nil { + if *conf.WT { + params = append(params, "wt") + } else { + params = append(params, "wb") + } + } + if conf.RA != nil { + if *conf.RA { + params = append(params, "ra") + } else { + params = append(params, "nora") + } + } + if conf.Direct != nil { + if *conf.Direct { + params = append(params, "direct") + } else { + params = append(params, "cached") + } + } + if conf.Cachedbadbbu != nil { + if *conf.Cachedbadbbu { + params = append(params, "CachedBadBBU") + } else { + params = append(params, "NoCachedBadBBU") + } + } + if conf.Strip != nil { + params = append(params, fmt.Sprintf("Strip=%d", *conf.Strip)) + } + return params +} + +func conf2Params(conf *baremetal.BaremetalDiskConfig) []string { + params := []string{} + if conf.WT != nil { + if *conf.WT { + params = append(params, "WT") + } else { + params = append(params, "WB") + } + } + if conf.RA != nil { + if *conf.RA { + params = append(params, "RA") + } else { + params = append(params, "NORA") + } + } + if conf.Direct != nil { + if *conf.Direct { + params = append(params, "Direct") + } else { + params = append(params, "Cached") + } + } + if conf.Cachedbadbbu != nil { + if *conf.Cachedbadbbu { + params = append(params, "CachedBadBBU") + } else { + params = append(params, "NoCachedBadBBU") + } + } + if conf.Strip != nil { + params = append(params, fmt.Sprintf("-strpsz%d", *conf.Strip)) + } + if len(conf.Size) > 0 { + for _, sz := range conf.Size { + params = append(params, fmt.Sprintf("-sz%d", sz)) + } + } + return params +} + +func (adapter *MegaRaidAdaptor) storcliBuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.storcliBuildRaid(devs, conf, 0) +} + +func (adapter *MegaRaidAdaptor) megacliBuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.megacliBuildRaid(devs, conf, 0) +} + +func (adapter *MegaRaidAdaptor) storcliBuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.storcliBuildRaid(devs, conf, 1) +} + +func (adapter *MegaRaidAdaptor) megacliBuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.megacliBuildRaid(devs, conf, 1) +} + +func (adapter *MegaRaidAdaptor) storcliBuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.storcliBuildRaid(devs, conf, 5) +} + +func (adapter *MegaRaidAdaptor) megacliBuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.megacliBuildRaid(devs, conf, 5) +} + +func (adapter *MegaRaidAdaptor) storcliBuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.storcliBuildRaid(devs, conf, 10) +} + +func (adapter *MegaRaidAdaptor) megacliBuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + if len(devs)%2 != 0 { + return fmt.Errorf("Odd number of %d devs", len(devs)) + } + devCnt := len(devs) / 2 + params := []string{} + for i := 0; i < devCnt; i++ { + d1 := devs[i] + d2 := devs[i+devCnt] + params = append(params, fmt.Sprintf("-Array%d[%s,%s]", i, GetSpecString(d1), GetSpecString(d2))) + } + args := []string{"-CfgSpanAdd", "-r10"} + args = append(args, params...) + args = append(args, conf2Params(conf)...) + args = append(args, fmt.Sprintf("-a%d", adapter.index)) + cmd := GetCommand(args...) + _, err := adapter.remoteRun(cmd) + return err +} + +func (adapter *MegaRaidAdaptor) storcliBuildRaid(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig, level uint) error { + args := []string{} + args = append(args, fmt.Sprintf("/c%d", adapter.index)) + args = append(args, "add", "vd", fmt.Sprintf("type=r%d", level)) + args = append(args, adapter.conf2ParamsStorcliSize(conf)...) + labels := []string{} + for _, dev := range devs { + labels = append(labels, GetSpecString(dev)) + } + args = append(args, fmt.Sprintf("drives=%s", strings.Join(labels, ","))) + if level == 10 { + args = append(args, "PDperArray=2") + } + args = append(args, adapter.conf2ParamsStorcli(conf)...) + cmd := GetCommand2(args...) + log.Infof("_storcliBuildRaid command: %s", cmd) + _, err := adapter.remoteRun(cmd) + return err +} + +func (adapter *MegaRaidAdaptor) megacliBuildRaid(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig, level uint) error { + labels := []string{} + for _, dev := range devs { + labels = append(labels, GetSpecString(dev)) + } + args := []string{"-CfgLdAdd", fmt.Sprintf("-r%d", level), fmt.Sprintf("[%s]", strings.Join(labels, ","))} + args = append(args, conf2Params(conf)...) + args = append(args, fmt.Sprintf("-a%d", adapter.index)) + cmd := GetCommand(args...) + log.Infof("_megacliBuildRaid command: %s", cmd) + _, err := adapter.remoteRun(cmd) + return err +} + +func cliBuildRaid( + devs []*baremetal.BaremetalStorage, + conf *baremetal.BaremetalDiskConfig, + funcs ...func([]*baremetal.BaremetalStorage, *baremetal.BaremetalDiskConfig) error, +) error { + var err error + for _, f := range funcs { + err = f(devs, conf) + if err == nil { + return nil + } + } + return err +} + +func (adapter *MegaRaidAdaptor) BuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return cliBuildRaid(devs, conf, adapter.megacliBuildRaid0, adapter.storcliBuildRaid0) +} + +func (adapter *MegaRaidAdaptor) BuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return cliBuildRaid(devs, conf, adapter.megacliBuildRaid1, adapter.storcliBuildRaid1) +} + +func (adapter *MegaRaidAdaptor) BuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return cliBuildRaid(devs, conf, adapter.megacliBuildRaid5, adapter.storcliBuildRaid5) +} + +func (adapter *MegaRaidAdaptor) BuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return cliBuildRaid(devs, conf, adapter.megacliBuildRaid10, adapter.storcliBuildRaid10) +} + +func (adapter *MegaRaidAdaptor) BuildNoneRaid(devs []*baremetal.BaremetalStorage) error { + return cliBuildRaid(devs, nil, adapter.megacliBuildNoRaid, adapter.storcliBuildNoRaid) +} + +func (adapter *MegaRaidAdaptor) storcliIsJBODEnabled() bool { + cmd := GetCommand2(fmt.Sprintf("/c%d", adapter.index), "show", "jbod") + lines, err := adapter.remoteRun(cmd) + if err != nil { + log.Errorf("storcliIsJBODEnabled error: %s", err) + return false + } + for _, line := range lines { + line = strings.ToLower(line) + if strings.HasPrefix(line, "jbod") { + data := strings.Split(line, " ") + if strings.TrimSpace(data[1]) == "on" { + return true + } + return false + } + } + return false +} + +func (adapter *MegaRaidAdaptor) storcliEnableJBOD(enable bool) bool { + val := "off" + if enable { + val = "on" + } + cmd := GetCommand2(fmt.Sprintf("/c%d", adapter.index), "set", fmt.Sprintf("jbod=%s", val)) + _, err := adapter.remoteRun(cmd) + if err != nil { + log.Errorf("EnableJBOD %v fail: %v", enable, err) + return false + } + return true +} + +func (adapter *MegaRaidAdaptor) storcliBuildJBOD(devs []*baremetal.BaremetalStorage) error { + if !adapter.storcliIsJBODEnabled() { + adapter.storcliEnableJBOD(true) + adapter.storcliEnableJBOD(false) + adapter.storcliEnableJBOD(true) + } + if !adapter.storcliIsJBODEnabled() { + return fmt.Errorf("JBOD not supported") + } + cmds := []string{} + for _, d := range devs { + cmd := GetCommand2(fmt.Sprintf("/c%d/e%d/s%d", adapter.index, d.Enclosure, d.Slot)) + cmds = append(cmds, cmd) + } + log.Infof("storcliBuildJBOD cmds: %v", cmds) + _, err := adapter.remoteRun(cmds...) + if err != nil { + return err + } + return nil +} + +func (adapter *MegaRaidAdaptor) storcliBuildNoRaid(devs []*baremetal.BaremetalStorage, _ *baremetal.BaremetalDiskConfig) error { + err := adapter.storcliBuildJBOD(devs) + if err == nil { + return nil + } + log.Errorf("Try build JBOD fail: %v", err) + labels := []string{} + for _, dev := range devs { + labels = append(labels, GetSpecString(dev)) + } + args := []string{ + fmt.Sprintf("/c%d", adapter.index), + "add", "vd", "each", "type=raid0", + fmt.Sprintf("drives=%s", strings.Join(labels, ",")), + "wt", "nora", "direct", + } + cmd := GetCommand2(args...) + _, err = adapter.remoteRun(cmd) + return err +} + +func (adapter *MegaRaidAdaptor) megacliBuildNoRaid(devs []*baremetal.BaremetalStorage, _ *baremetal.BaremetalDiskConfig) error { + err := adapter.megacliBuildJBOD(devs) + if err == nil { + return nil + } + log.Errorf("Try build jbod fail: %v", err) + cmds := []string{} + for _, dev := range devs { + cmd := GetCommand("-CfgLdAdd", "-r0", fmt.Sprintf("[%s]", GetSpecString(dev)), + "WT", "NORA", "Direct", "NoCachedBadBBU", fmt.Sprintf("-a%d", adapter.index)) + cmds = append(cmds, cmd) + } + _, err = adapter.remoteRun(cmds...) + return err +} + +func (adapter *MegaRaidAdaptor) megacliIsJBODEnabled() bool { + cmd := GetCommand("-AdpGetProp", "-EnableJBOD", fmt.Sprintf("-a%d", adapter.index)) + pref := fmt.Sprintf("Adapter %d: JBOD: ", adapter.index) + lines, err := adapter.remoteRun(cmd) + if err != nil { + log.Errorf("megacliIsJBODEnabled error: %v", err) + return false + } + for _, line := range lines { + if strings.HasPrefix(line, pref) { + val := strings.ToLower(strings.TrimSpace(line[len(pref):])) + if val == "disabled" { + return false + } + return true + } + } + return false +} + +func (adapter *MegaRaidAdaptor) megacliEnableJBOD(enable bool) bool { + var val string = "0" + if enable { + val = "1" + } + cmd := GetCommand("-AdpSetProp", "-EnableJBOD", fmt.Sprintf("-%s", val), fmt.Sprintf("-a%d", adapter.index)) + _, err := adapter.remoteRun(cmd) + if err != nil { + log.Errorf("enable jbod %v fail: %v", enable, err) + return false + } + return true +} + +func (adapter *MegaRaidAdaptor) megacliBuildJBOD(devs []*baremetal.BaremetalStorage) error { + if !adapter.megacliIsJBODEnabled() { + adapter.megacliEnableJBOD(true) + adapter.megacliEnableJBOD(false) + adapter.megacliEnableJBOD(true) + } + if !adapter.megacliIsJBODEnabled() { + return fmt.Errorf("JBOD not supported") + } + devIds := []string{} + for _, d := range devs { + devIds = append(devIds, GetSpecString(d)) + } + cmd := GetCommand("-PDMakeJBOD", fmt.Sprintf("-PhysDrv[%s]", strings.Join(devIds, ",")), fmt.Sprintf("-a%d", adapter.index)) + _, err := adapter.remoteRun(cmd) + return err +} + +func (adapter *MegaRaidAdaptor) RemoveLogicVolumes() error { + cmds := []string{} + lvIdx, err := adapter.GetLogicVolumes() + if err != nil { + return err + } + for i := len(lvIdx) - 1; i >= 0; i-- { + cmd := GetCommand("-CfgLdDel", fmt.Sprintf("-L%d", i), "-Force", fmt.Sprintf("-a%d", adapter.index)) + cmds = append(cmds, cmd) + } + if len(cmds) > 0 { + _, err := adapter.remoteRun(cmds...) + if err != nil { + return err + } + return nil + } + return nil +} + +/* +def _storcli_clear_jbod_disks(self): + cmds = [] + for dev in self.devs: + cmd = self.raid.get_command2( + '/c%d/e%d/s%d' % (self.index, dev.enclosure, dev.slot), + 'set', 'good', 'force') + cmds.append(cmd) + logging.info('%s', cmds) + self.raid.term.exec_remote_commands(cmds) +*/ + +func (adapter *MegaRaidAdaptor) megacliClearJBODDisks() error { + devIds := []string{} + for _, dev := range adapter.devs { + devIds = append(devIds, GetSpecString(dev.ToBaremetalStorage())) + } + cmd := GetCommand("-PDMakeGood", fmt.Sprintf("-PhysDrv[%s]", strings.Join(devIds, ",")), "-Force", fmt.Sprintf("-a%d", adapter.index)) + _, err := adapter.remoteRun(cmd) + return err +} + +func (adapter *MegaRaidAdaptor) clearJBODDisks() { + adapter.megacliEnableJBOD(true) + adapter.megacliEnableJBOD(false) + adapter.megacliEnableJBOD(true) + adapter.megacliEnableJBOD(false) +} + +type MegaRaid struct { + term *ssh.Client + adapters []*MegaRaidAdaptor + PhyDevsCnt int + Capacity int64 +} + +func NewMegaRaid(term *ssh.Client) raiddrivers.IRaidDriver { + return &MegaRaid{ + term: term, + adapters: make([]*MegaRaidAdaptor, 0), + } +} + +func GetCommand(args ...string) string { + bin := "/opt/MegaRAID/MegaCli/MegaCli64" + return raiddrivers.GetCommand(bin, args...) +} + +func GetCommand2(args ...string) string { + bin := "/opt/MegaRAID/storcli/storcli64" + return raiddrivers.GetCommand(bin, args...) +} + +func (raid *MegaRaid) GetName() string { + return baremetal.DISK_DRIVER_MEGARAID +} + +func (raid *MegaRaid) ParsePhyDevs() error { + if !utils.IsInStringArray("megaraid_sas", raiddrivers.GetModules(raid.term)) { + return fmt.Errorf("Not found megaraid_sas module") + } + cmd := GetCommand("-PDList", "-aALL") + ret, err := raid.term.Run(cmd) + if err != nil { + return fmt.Errorf("List raid disk error: %v", err) + } + err = raid.parsePhyDevs(ret) + if err != nil { + return fmt.Errorf("parse physical disk device error: %v", err) + } + return nil +} + +func (raid *MegaRaid) parsePhyDevs(lines []string) error { + phyDev := NewMegaRaidPhyDev() + var adapter *MegaRaidAdaptor + for _, line := range lines { + matches := adapterPatter.FindStringSubmatch(line) + if len(matches) != 0 { + paramsMap := make(map[string]string) + for i, name := range sizePattern.SubexpNames() { + if i > 0 && i <= len(matches) { + paramsMap[name] = matches[i] + } + } + adapterStr := paramsMap["idx"] + adapterInt, _ := strconv.Atoi(adapterStr) + adapter = NewMegaRaidAdaptor(adapterInt, raid) + raid.adapters = append(raid.adapters, adapter) + } else if phyDev.parseLine(line) && phyDev.isComplete() { + if adapter == nil { + return fmt.Errorf("Adapter is nil") + } + adapter.AddPhyDev(phyDev) + raid.PhyDevsCnt += 1 + raid.Capacity += phyDev.GetSize() + phyDev = NewMegaRaidPhyDev() + } + } + for _, dev := range adapter.devs { + if err := raid.addPhyDevStripSize(dev); err != nil { + return fmt.Errorf("addPhyDevStripSize: %v", err) + } + } + return nil +} + +func (raid *MegaRaid) addPhyDevStripSize(phyDev *MegaRaidPhyDev) error { + grepCmd := []string{"grep", "-iE", "'^(Min|Max) Strip Size'"} + args := []string{"-adpallinfo", "-aall", "|"} + args = append(args, grepCmd...) + cmd := GetCommand(args...) + ret, err := raid.term.Run(cmd) + if err != nil { + return fmt.Errorf("addPhyDevStripSize error: %v", err) + } + return phyDev.parseStripSize(ret) +} + +/*func (raid *MegaRaid) GetPhyDevs() []*MegaRaidPhyDev { + devs := make([]*MegaRaidPhyDev, 0) + for _, ada := range raid.adapters { + devs = append(devs, ada.GetPhyDevs()...) + } + return devs +}*/ + +//func (raid *MegaRaid) ParseLogicVolumes() bool { +//cmd := GetCommand("-LDInfo", "-Lall", "-aALL") +//ret, err := raid.term.Run(cmd) +//if err != nil { +//return false +//} +//raid.parseLogicVolumes(ret) +//return true +//} + +func (raid *MegaRaid) CleanRaid() error { + for _, adapter := range raid.adapters { + adapter.clearJBODDisks() + adapter.RemoveLogicVolumes() + } + return nil +} + +func (raid *MegaRaid) PreBuildRaid(_ []*baremetal.BaremetalDiskConfig, _ int) error { + return raid.clearForeignState() +} + +func (raid *MegaRaid) GetAdapters() []raiddrivers.IRaidAdapter { + ret := make([]raiddrivers.IRaidAdapter, 0) + for _, a := range raid.adapters { + ret = append(ret, a) + } + return ret +} + +func (raid *MegaRaid) clearForeignState() error { + cmd := GetCommand("-CfgForeign", "-Clear", "-aALL") + _, err := raid.term.Run(cmd) + return err +} + +func (raid *MegaRaid) RemoveLogicVolumes() { + for _, adapter := range raid.adapters { + adapter.RemoveLogicVolumes() + } +} + +func init() { + raiddrivers.RegisterDriver(baremetal.DISK_DRIVER_MEGARAID, NewMegaRaid) +} diff --git a/pkg/baremetal/utils/raid/mvcli/doc.go b/pkg/baremetal/utils/raid/mvcli/doc.go new file mode 100644 index 0000000000..a99dbbdce6 --- /dev/null +++ b/pkg/baremetal/utils/raid/mvcli/doc.go @@ -0,0 +1 @@ +package mvcli // import "yunion.io/x/onecloud/pkg/baremetal/utils/raid/mvcli" diff --git a/pkg/baremetal/utils/raid/mvcli/mvcli.go b/pkg/baremetal/utils/raid/mvcli/mvcli.go new file mode 100644 index 0000000000..be712c5957 --- /dev/null +++ b/pkg/baremetal/utils/raid/mvcli/mvcli.go @@ -0,0 +1,298 @@ +package mvcli + +import ( + "fmt" + "strconv" + "strings" + + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/stringutils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/raid" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type MarvelRaidPhyDev struct { + *raid.RaidBasePhyDev + slot int + sn string +} + +func NewMarvelRaidPhyDev(adapter int) *MarvelRaidPhyDev { + b := raid.NewRaidBasePhyDev(baremetal.DISK_DRIVER_MARVELRAID) + b.Adapter = adapter + return &MarvelRaidPhyDev{ + RaidBasePhyDev: b, + slot: -1, + } +} + +func (dev *MarvelRaidPhyDev) parseLine(line string) bool { + key, val := stringutils.SplitKeyValue(line) + if key == "" { + return false + } + switch key { + case "SSD Type": + if strings.HasSuffix(val, "SSD") { + dev.Rotate = tristate.True + } else { + dev.Rotate = tristate.False + } + case "PD ID": + dev.slot, _ = strconv.Atoi(val) + case "Size": + dat := strings.Split(val, " ") + size, _ := strconv.Atoi(dat[0]) + dev.Size = int64(size / 1024) // MB + case "model": + dev.Model = val + case "Serial": + dev.sn = val + default: + return false + } + return true +} + +func (dev *MarvelRaidPhyDev) isComplete() bool { + if !dev.RaidBasePhyDev.IsComplete() { + return false + } + if dev.Size < 0 { + return false + } + if dev.slot < 0 { + return false + } + if dev.sn == "" { + return false + } + return true +} + +func (dev *MarvelRaidPhyDev) ToBaremetalStorage() *baremetal.BaremetalStorage { + s := dev.RaidBasePhyDev.ToBaremetalStorage() + s.Slot = dev.slot + return s +} + +func GetSpecString(dev *baremetal.BaremetalStorage) string { + return fmt.Sprintf("%d", dev.Slot) +} + +type MarvelRaidAdaptor struct { + index int + raid *MarvelRaid + devs []*MarvelRaidPhyDev +} + +func NewMarvelRaidAdaptor(index int, raid *MarvelRaid) *MarvelRaidAdaptor { + return &MarvelRaidAdaptor{ + index: index, + raid: raid, + devs: make([]*MarvelRaidPhyDev, 0), + } +} + +func (adapter *MarvelRaidAdaptor) GetIndex() int { + return adapter.index +} + +func (adapter *MarvelRaidAdaptor) ParsePhyDevs() error { + cmd := GetCommand("info", "-o", "pd") + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return fmt.Errorf("get physical device: %v", err) + } + return adapter.parsePhyDevs(ret) +} + +func (adapter *MarvelRaidAdaptor) parsePhyDevs(lines []string) error { + phyDev := NewMarvelRaidPhyDev(adapter.index) + for _, line := range lines { + if phyDev.parseLine(line) && phyDev.isComplete() { + adapter.devs = append(adapter.devs, phyDev) + phyDev = NewMarvelRaidPhyDev(adapter.index) + } + } + return nil +} + +func (adapter *MarvelRaidAdaptor) GetDevices() []*baremetal.BaremetalStorage { + ret := []*baremetal.BaremetalStorage{} + for _, dev := range adapter.devs { + ret = append(ret, dev.ToBaremetalStorage()) + } + return ret +} + +func (adapter *MarvelRaidAdaptor) GetLogicVolumes() ([]int, error) { + cmd := GetCommand("info", "-o", "vd") + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return nil, fmt.Errorf("getLogicVolumes: %v", err) + } + return adapter.parseLogicVolumes(ret) +} + +func (adapter *MarvelRaidAdaptor) parseLogicVolumes(lines []string) ([]int, error) { + lvIdx := []int{} + usedDevs := []int{} + for _, line := range lines { + key, val := stringutils.SplitKeyValue(line) + if key != "" { + if key == "id" { + idx, err := strconv.Atoi(val) + if err != nil { + return nil, err + } + lvIdx = append(lvIdx, idx) + } else if key == "PD RAID setup" { + for _, d := range strings.Split(val, " ") { + idx, err := strconv.Atoi(d) + if err != nil { + return nil, err + } + usedDevs = append(usedDevs, idx) + } + } + } + } + if len(adapter.devs) < len(usedDevs) { + return nil, fmt.Errorf("adapter %d current %d devs < usedDevs %d", adapter.index, len(adapter.devs), len(usedDevs)) + } + return lvIdx, nil +} + +func (adapter *MarvelRaidAdaptor) RemoveLogicVolumes() error { + lvs, err := adapter.GetLogicVolumes() + if err != nil { + return fmt.Errorf("Failed to get logic volumes: %v", err) + } + for i := len(lvs) - 1; i >= 0; i-- { + if err := adapter.removeLogicVolume(i); err != nil { + return fmt.Errorf("Remove %d logical volume: %v", i, err) + } + } + return nil +} + +func (adapter *MarvelRaidAdaptor) removeLogicVolume(idx int) error { + cmd := GetCommand("delete", "-o", "vd", "-i", fmt.Sprintf("%d", idx), "-f", "--waiveconfirmation") + _, err := adapter.raid.term.Run(cmd) + return err +} + +func (adapter *MarvelRaidAdaptor) PreBuildRaid(confs []*baremetal.BaremetalDiskConfig) error { + return nil +} + +func (adapter *MarvelRaidAdaptor) buildRaid(level string, devs []*baremetal.BaremetalStorage, _ *baremetal.BaremetalDiskConfig) error { + pds := []string{} + for _, dev := range devs { + pds = append(pds, fmt.Sprintf("%s", GetSpecString(dev))) + } + args := []string{"create", "-o", "vd", "-d", strings.Join(pds, ","), level, "--waiveconfirmation"} + cmd := GetCommand(args...) + _, err := adapter.raid.term.Run(cmd) + return err +} + +func (adapter *MarvelRaidAdaptor) BuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("-r0", devs, conf) +} + +func (adapter *MarvelRaidAdaptor) BuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("-r1", devs, conf) +} + +func (adapter *MarvelRaidAdaptor) BuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + //return adapter.buildRaid("-r5", devs, conf) + return fmt.Errorf("BuildRaid5 not impl") +} + +func (adapter *MarvelRaidAdaptor) BuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("-r10", devs, conf) +} + +func (adapter *MarvelRaidAdaptor) BuildNoneRaid(devs []*baremetal.BaremetalStorage) error { + return fmt.Errorf("BuildNoneRaid not impl") +} + +type MarvelRaid struct { + term *ssh.Client + adapters []*MarvelRaidAdaptor +} + +func NewMarvelRaid(term *ssh.Client) raid.IRaidDriver { + return &MarvelRaid{ + term: term, + adapters: make([]*MarvelRaidAdaptor, 0), + } +} + +func (r *MarvelRaid) GetName() string { + return baremetal.DISK_DRIVER_MARVELRAID +} + +func GetCommand(args ...string) string { + bin := "/opt/mvcli/mvcli" + return raid.GetCommand(bin, args...) +} + +func (r *MarvelRaid) ParsePhyDevs() error { + cmd := GetCommand("info", "-o", "hba") + ret, err := r.term.Run(cmd) + if err != nil { + return fmt.Errorf("Remote get info error: %v", err) + } + err = r.parseAdapters(ret) + if err != nil { + return fmt.Errorf("parse adapt error: %v", err) + } + if len(r.adapters) > 0 { + return nil + } + return fmt.Errorf("Empty adapters") +} + +func (r *MarvelRaid) parseAdapters(lines []string) error { + for _, line := range lines { + k, v := stringutils.SplitKeyValue(line) + if k == "Adapter ID" { + vi, err := strconv.Atoi(v) + if err != nil { + return err + } + adapter := NewMarvelRaidAdaptor(vi, r) + r.adapters = append(r.adapters, adapter) + } + } + for _, adp := range r.adapters { + adp.ParsePhyDevs() + } + return nil +} + +func (r *MarvelRaid) PreBuildRaid(_ []*baremetal.BaremetalDiskConfig, _ int) error { + return nil +} + +func (r *MarvelRaid) GetAdapters() []raid.IRaidAdapter { + ret := make([]raid.IRaidAdapter, 0) + for _, a := range r.adapters { + ret = append(ret, a) + } + return ret +} + +func (r *MarvelRaid) CleanRaid() error { + // pass + return nil +} + +func init() { + raid.RegisterDriver(baremetal.DISK_DRIVER_MARVELRAID, NewMarvelRaid) +} diff --git a/pkg/baremetal/utils/raid/raid.go b/pkg/baremetal/utils/raid/raid.go new file mode 100644 index 0000000000..89e00b5ac4 --- /dev/null +++ b/pkg/baremetal/utils/raid/raid.go @@ -0,0 +1,91 @@ +package raid + +import ( + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/tristate" + + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type RaidDriverFactory func(term *ssh.Client) IRaidDriver + +type sRaidDrivers map[string]RaidDriverFactory + +var RaidDrivers sRaidDrivers + +func init() { + RaidDrivers = make(map[string]RaidDriverFactory) +} + +func GetCommand(bin string, args ...string) string { + cmd := []string{bin} + cmd = append(cmd, args...) + return strings.Join(cmd, " ") +} + +func RegisterDriver(name string, drv RaidDriverFactory) { + RaidDrivers[name] = drv +} + +type RaidBasePhyDev struct { + Adapter int + Size int64 + Model string + Rotate tristate.TriState + Status string + Driver string +} + +func NewRaidBasePhyDev(driver string) *RaidBasePhyDev { + return &RaidBasePhyDev{ + Size: -1, + Rotate: tristate.None, + Driver: driver, + } +} + +func (dev *RaidBasePhyDev) IsComplete() bool { + if dev.Model == "" { + return false + } + if dev.Rotate.IsNone() { + return false + } + if dev.Status == "" { + return false + } + return true +} + +func (dev *RaidBasePhyDev) ToBaremetalStorage() *baremetal.BaremetalStorage { + return &baremetal.BaremetalStorage{ + Adapter: dev.Adapter, + Status: dev.Status, + Size: dev.Size, + Model: dev.Model, + Rotate: dev.Rotate.Bool(), + Driver: dev.Driver, + } +} + +func GetModules(term *ssh.Client) []string { + ret := []string{} + lines, err := term.Run("/sbin/lsmod") + if err != nil { + log.Errorf("Remote lsmod error: %v", err) + return ret + } + for _, line := range lines { + if len(line) == 0 { + continue + } + mod := line[:strings.Index(line, " ")] + if mod != "Module" { + ret = append(ret, mod) + } + } + return ret +} diff --git a/pkg/baremetal/utils/raid/sas2iru/doc.go b/pkg/baremetal/utils/raid/sas2iru/doc.go new file mode 100644 index 0000000000..fa8a16a76b --- /dev/null +++ b/pkg/baremetal/utils/raid/sas2iru/doc.go @@ -0,0 +1 @@ +package sas2iru // import "yunion.io/x/onecloud/pkg/baremetal/utils/raid/sas2iru" diff --git a/pkg/baremetal/utils/raid/sas2iru/sas2iru.go b/pkg/baremetal/utils/raid/sas2iru/sas2iru.go new file mode 100644 index 0000000000..bdc2f5b1f1 --- /dev/null +++ b/pkg/baremetal/utils/raid/sas2iru/sas2iru.go @@ -0,0 +1,363 @@ +package sas2iru + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/stringutils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/raid" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type Mpt2SASRaidPhyDev struct { + *raid.RaidBasePhyDev + + enclosure int + slot int + sector int + block int +} + +func newMpt2SASRaidPhyDev(adapter int) *Mpt2SASRaidPhyDev { + b := raid.NewRaidBasePhyDev(baremetal.DISK_DRIVER_MARVELRAID) + b.Adapter = adapter + return &Mpt2SASRaidPhyDev{ + RaidBasePhyDev: b, + slot: -1, + enclosure: -1, + block: -1, + sector: -1, + } +} + +func (dev *Mpt2SASRaidPhyDev) parseLine(line string) bool { + key, val := stringutils.SplitKeyValue(line) + if key == "" { + return false + } + switch key { + case "Drive Type": + if strings.HasSuffix("_HDD", val) { + dev.Rotate = tristate.True + } else { + dev.Rotate = tristate.False + } + case "Enclosure #": + dev.enclosure, _ = strconv.Atoi(val) + case "Slot #": + dev.slot, _ = strconv.Atoi(val) + case "Size (in MB)/(in sectors)": + dat := strings.Split(val, "/") + sz, _ := strconv.Atoi(dat[0]) + dev.Size = int64(sz) + dev.sector, _ = strconv.Atoi(dat[1]) + dev.block = int(dev.Size * 1024 * 1024 / 7814037167) + if dev.block > 4000 { + dev.block = 4096 + } else { + dev.block = 512 + } + dev.Size = int64(dev.block * dev.sector / 1024 / 1024) + case "Manufacturer", "Model Number", "Firmware Revision", "Serial No": + if dev.Model == "" { + dev.Model = val + } else { + dev.Model = fmt.Sprintf("%s %s", dev.Model, val) + } + case "State": + if strings.Contains(strings.ToLower(val), "ready") { + dev.Status = "online" + } else { + dev.Status = strings.ToLower(val) + } + default: + return false + } + return true +} + +func (dev *Mpt2SASRaidPhyDev) isComplete() bool { + if !dev.RaidBasePhyDev.IsComplete() { + return false + } + if dev.Size < 0 { + return false + } + if dev.slot < 0 { + return false + } + if dev.sector < 0 { + return false + } + if dev.block < 0 { + return false + } + return true +} + +func (dev *Mpt2SASRaidPhyDev) ToBaremetalStorage() *baremetal.BaremetalStorage { + s := dev.RaidBasePhyDev.ToBaremetalStorage() + s.Slot = dev.slot + s.Enclosure = dev.enclosure + s.Block = int64(dev.block) + s.Sector = int64(dev.sector) + return s +} + +func GetSpecString(dev *baremetal.BaremetalStorage) string { + if dev.Enclosure < 0 { + return fmt.Sprintf(":%d", dev.Slot) + } + return fmt.Sprintf("%d:%d", dev.Enclosure, dev.Slot) +} + +type Mpt2SASRaidAdaptor struct { + index int + raid *Mpt2SASRaid + devs []*Mpt2SASRaidPhyDev +} + +func newMpt2SASRaidAdaptor(index int, raid *Mpt2SASRaid) *Mpt2SASRaidAdaptor { + return &Mpt2SASRaidAdaptor{ + index: index, + raid: raid, + devs: make([]*Mpt2SASRaidPhyDev, 0), + } +} + +func (adapter *Mpt2SASRaidAdaptor) GetIndex() int { + return adapter.index +} + +func (adapter *Mpt2SASRaidAdaptor) ParsePhyDevs() error { + cmd := adapter.raid.GetCommand(fmt.Sprintf("%d", adapter.index), "DISPLAY") + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return fmt.Errorf("get physical device: %v", err) + } + for _, l := range ret { + if strings.Contains(l, "RAID Support") && strings.ToLower(strings.TrimSpace(l[strings.Index(l, ":")+1:])) == "no" { + return fmt.Errorf("No raid support") + } + } + return adapter.parsePhyDevs(ret) +} + +func (adapter *Mpt2SASRaidAdaptor) parsePhyDevs(lines []string) error { + dev := newMpt2SASRaidPhyDev(adapter.index) + for _, l := range lines { + if dev.parseLine(l) && dev.isComplete() { + adapter.devs = append(adapter.devs, dev) + dev = newMpt2SASRaidPhyDev(adapter.index) + } + } + return nil +} + +func (adapter *Mpt2SASRaidAdaptor) GetDevices() []*baremetal.BaremetalStorage { + ret := []*baremetal.BaremetalStorage{} + for _, dev := range adapter.devs { + ret = append(ret, dev.ToBaremetalStorage()) + } + return ret +} + +func (adapter *Mpt2SASRaidAdaptor) GetLogicVolumes() ([]int, error) { + cmd := adapter.raid.GetCommand(fmt.Sprintf("%d", adapter.index), "DISPLAY") + ret, err := adapter.raid.term.Run(cmd) + if err != nil { + return nil, fmt.Errorf("GetLogicVolumes error: %v", err) + } + return adapter.parseLogicVolumes(ret) +} + +func (adapter *Mpt2SASRaidAdaptor) parseLogicVolumes(lines []string) ([]int, error) { + lvIdx := []int{} + usedDevs := []int{} + for _, line := range lines { + key, val := stringutils.SplitKeyValue(line) + if key != "" && key == "Volume ID" { + idx, _ := strconv.Atoi(val) + lvIdx = append(lvIdx, idx) + } else if regexp.MustCompile(`PHY\[\d+\] Enclosure#/Slot#`).MatchString(key) { + idx, _ := strconv.Atoi(val) + usedDevs = append(usedDevs, idx) + } + } + if len(adapter.devs) < len(usedDevs) { + return nil, fmt.Errorf("adapter current dev %d < usedDevs %d", len(adapter.devs), len(usedDevs)) + } + for i := 0; i < len(adapter.devs)-len(usedDevs); i++ { + lvIdx = append(lvIdx, -1) + } + return lvIdx, nil +} + +func (adapter *Mpt2SASRaidAdaptor) rescanLV() error { + var cmd string + if adapter.raid.utility == "/opt/lsi/sas2ircu" { + cmd = "/opt/lsi/rescan.sh mpt2sas" + } else if adapter.raid.utility == "/opt/lsi/sas3ircu" { + cmd = "/opt/lsi/rescan.sh mpt3sas" + } else { + return fmt.Errorf("Unsupport raid utility: %v", adapter.raid.utility) + } + _, err := adapter.raid.term.Run(cmd) + return err +} + +func (adapter *Mpt2SASRaidAdaptor) setBootIR() error { + lvs, err := adapter.GetLogicVolumes() + if err != nil { + return err + } + if len(lvs) > 0 && lvs[0] > 0 { + args := []string{fmt.Sprintf("%d", adapter.index), "BOOTIR", fmt.Sprintf("%d", lvs[0])} + cmd := adapter.raid.GetCommand(args...) + _, err := adapter.raid.term.Run(cmd) + return err + } + return nil +} + +func (adapter *Mpt2SASRaidAdaptor) PreBuildRaid(confs []*baremetal.BaremetalDiskConfig) error { + return nil +} + +func (adapter *Mpt2SASRaidAdaptor) buildRaid(level string, devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + if len(conf.Size) > 1 { + return fmt.Errorf("Subdivide sub-size not supported") + } + args := []string{fmt.Sprintf("%d", adapter.index), "CREATE", level, "MAX"} + labels := []string{} + for _, dev := range devs { + labels = append(labels, GetSpecString(dev)) + } + args = append(args, labels...) + args = append(args, "noprompt") + _, err := adapter.raid.term.Run(adapter.raid.GetCommand(args...)) + if err != nil { + return fmt.Errorf("Build raid error: %v", err) + } + if err := adapter.setBootIR(); err != nil { + return fmt.Errorf("setBootIR: %v", err) + } + if err := adapter.rescanLV(); err != nil { + return fmt.Errorf("rescanLV: %v", err) + } + return nil +} + +func (adapter *Mpt2SASRaidAdaptor) BuildRaid0(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("RAID0", devs, conf) +} + +func (adapter *Mpt2SASRaidAdaptor) BuildRaid1(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return adapter.buildRaid("RAID1", devs, conf) +} + +func (adapter *Mpt2SASRaidAdaptor) BuildRaid5(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + return fmt.Errorf("Not impl") +} + +func (adapter *Mpt2SASRaidAdaptor) BuildRaid10(devs []*baremetal.BaremetalStorage, conf *baremetal.BaremetalDiskConfig) error { + if len(devs) > 10 { + return fmt.Errorf("RAID10 supports no more than 10 disks") + } + return adapter.buildRaid("RAID10", devs, conf) +} + +func (adapter *Mpt2SASRaidAdaptor) BuildNoneRaid(devs []*baremetal.BaremetalStorage) error { + return fmt.Errorf("Not impl") +} + +func (adapter *Mpt2SASRaidAdaptor) RemoveLogicVolumes() error { + cmd := adapter.raid.GetCommand(fmt.Sprintf("%d", adapter.index), "DELETE", "noprompt") + _, err := adapter.raid.term.Run(cmd) + return err +} + +type Mpt2SASRaid struct { + term *ssh.Client + utility string + adapters []*Mpt2SASRaidAdaptor +} + +func NewMpt2SASRaid(term *ssh.Client) raid.IRaidDriver { + return &Mpt2SASRaid{ + term: term, + adapters: make([]*Mpt2SASRaidAdaptor, 0), + } +} + +func (r *Mpt2SASRaid) GetName() string { + return baremetal.DISK_DRIVER_MPT2SAS +} + +func (r *Mpt2SASRaid) ParsePhyDevs() error { + if r.modulePCIProbed("mpt2sas") { + r.utility = "/opt/lsi/sas2ircu" + } else if r.modulePCIProbed("mpt3sas") { + r.utility = "/opt/lsi/sas3ircu" + } else { + return fmt.Errorf("Not probe mpt2sas or mpt3sas kernel module") + } + cmd := r.GetCommand("LIST") + ret, err := r.term.Run(cmd) + if err != nil { + return err + } + return r.parseAdapters(ret) +} + +func (r *Mpt2SASRaid) parseAdapters(lines []string) error { + for _, line := range lines { + dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1) + if regexp.MustCompile(`\d+`).MatchString(dat[0]) { + idx, _ := strconv.Atoi(dat[0]) + adapter := newMpt2SASRaidAdaptor(idx, r) + r.adapters = append(r.adapters, adapter) + } + } + for _, adapter := range r.adapters { + if err := adapter.ParsePhyDevs(); err != nil { + return err + } + } + return nil +} + +func (r *Mpt2SASRaid) modulePCIProbed(mod string) bool { + cmd := fmt.Sprintf("lspci -k | grep %s", mod) + _, err := r.term.Run(cmd) + return err == nil +} + +func (r *Mpt2SASRaid) GetCommand(args ...string) string { + return raid.GetCommand(r.utility, args...) +} + +func (r *Mpt2SASRaid) PreBuildRaid(_ []*baremetal.BaremetalDiskConfig, _ int) error { + return nil +} + +func (r *Mpt2SASRaid) GetAdapters() []raid.IRaidAdapter { + ret := make([]raid.IRaidAdapter, 0) + for _, a := range r.adapters { + ret = append(ret, a) + } + return ret +} + +func (r *Mpt2SASRaid) CleanRaid() error { + return nil +} + +func init() { + raid.RegisterDriver(baremetal.DISK_DRIVER_MPT2SAS, NewMpt2SASRaid) +} diff --git a/pkg/cloudcommon/app.go b/pkg/cloudcommon/app.go index 6e53546e49..6463dfbbea 100644 --- a/pkg/cloudcommon/app.go +++ b/pkg/cloudcommon/app.go @@ -23,7 +23,11 @@ func InitApp(options *CommonOptions, dbAccess bool) *appsrv.Application { return app } -func ServeForever(app *appsrv.Application, options *CommonOptions, onStop func()) { +func ServeForever(app *appsrv.Application, options *CommonOptions) { + ServeForeverWithCleanup(app, options, nil) +} + +func ServeForeverWithCleanup(app *appsrv.Application, options *CommonOptions, onStop func()) { AppDBInit(app) addr := net.JoinHostPort(options.Address, strconv.Itoa(options.Port)) proto := "http" @@ -33,9 +37,9 @@ func ServeForever(app *appsrv.Application, options *CommonOptions, onStop func() log.Infof("Start listen on %s://%s", proto, addr) if options.EnableSsl { certfile := options.SslCertfile - if len(options.SslCafile) > 0 { + if len(options.SslCaCerts) > 0 { var err error - certfile, err = seclib2.MergeCaCertFiles(options.SslCafile, options.SslCertfile) + certfile, err = seclib2.MergeCaCertFiles(options.SslCaCerts, options.SslCertfile) if err != nil { log.Fatalf("fail to merge ca+cert content: %s", err) } @@ -47,8 +51,8 @@ func ServeForever(app *appsrv.Application, options *CommonOptions, onStop func() if len(options.SslKeyfile) == 0 { log.Fatalf("Missing ssl-keyfile") } - app.ListenAndServeTLS(addr, certfile, options.SslKeyfile, onStop) + app.ListenAndServeTLSWithCleanup(addr, certfile, options.SslKeyfile, onStop) } else { - app.ListenAndServe(addr, onStop) + app.ListenAndServeWithCleanup(addr, onStop) } } diff --git a/pkg/cloudcommon/consts/notify.go b/pkg/cloudcommon/consts/notify.go new file mode 100644 index 0000000000..f993d7b1fd --- /dev/null +++ b/pkg/cloudcommon/consts/notify.go @@ -0,0 +1,9 @@ +package consts + +var ( + NotifyTemplateDir = "/opt/yunion/share/notify_templates" +) + +func SetNotifyTemplateDir(dir string) { + NotifyTemplateDir = dir +} diff --git a/pkg/cloudcommon/consts/opslog.go b/pkg/cloudcommon/consts/opslog.go new file mode 100644 index 0000000000..e19769d72a --- /dev/null +++ b/pkg/cloudcommon/consts/opslog.go @@ -0,0 +1,13 @@ +package consts + +var ( + globalOpsLogEnabled = true +) + +func DisableOpsLog() { + globalOpsLogEnabled = false +} + +func OpsLogEnabled() bool { + return globalOpsLogEnabled +} diff --git a/pkg/cloudcommon/cronman/cronman.go b/pkg/cloudcommon/cronman/cronman.go index d2bfe0aa29..f9fa6cc936 100644 --- a/pkg/cloudcommon/cronman/cronman.go +++ b/pkg/cloudcommon/cronman/cronman.go @@ -17,13 +17,6 @@ type TCronJobFunction func(ctx context.Context, userCred mcclient.TokenCredentia var manager *SCronJobManager -func init() { - manager = &SCronJobManager{ - jobs: make([]*SCronJob, 0), - workers: appsrv.NewWorkerManager("CronJobWorkers", 4, 1024, true), - } -} - type ICronTimer interface { Next(time.Time) time.Time } @@ -93,7 +86,14 @@ type SCronJobManager struct { workers *appsrv.SWorkerManager } -func GetCronJobManager() *SCronJobManager { +func GetCronJobManager(idDbWorker bool) *SCronJobManager { + if manager == nil { + manager = &SCronJobManager{ + jobs: make([]*SCronJob, 0), + workers: appsrv.NewWorkerManager("CronJobWorkers", 4, 1024, idDbWorker), + } + } + return manager } diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index db89340d58..8734f564ce 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -753,8 +753,16 @@ func fetchOwnerProjectId(ctx context.Context, manager IModelManager, userCred mc if data != nil { projId = jsonutils.GetAnyString(data, []string{"project", "tenant", "project_id", "tenant_id"}) } + ownerProjId := manager.GetOwnerId(userCred) if len(projId) == 0 { - return manager.GetOwnerId(userCred), nil + return ownerProjId, nil + } + t, _ := TenantCacheManager.FetchTenantByIdOrName(ctx, projId) + if t == nil { + return "", httperrors.NewNotFoundError("Project %s not found", projId) + } + if t.GetId() == ownerProjId { + return ownerProjId, nil } var isAllow bool if consts.IsRbacEnabled() { @@ -769,10 +777,6 @@ func fetchOwnerProjectId(ctx context.Context, manager IModelManager, userCred mc if !isAllow { return "", httperrors.NewForbiddenError("Delegation not allowed") } - t, _ := TenantCacheManager.FetchTenantByIdOrName(ctx, projId) - if t == nil { - return "", httperrors.NewNotFoundError("Project %s not found", projId) - } return t.GetId(), nil } @@ -811,8 +815,19 @@ func FetchModelObjects(modelManager IModelManager, query *sqlchemy.SQuery, targe return nil } +func DoCreate(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject, ownerProjId string) (IModel, error) { + lockman.LockClass(ctx, manager, ownerProjId) + defer lockman.ReleaseClass(ctx, manager, ownerProjId) + + return doCreateItem(manager, ctx, userCred, ownerProjId, nil, data) +} + func doCreateItem(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) (IModel, error) { - dataDict := data.(*jsonutils.JSONDict) + dataDict, ok := data.(*jsonutils.JSONDict) + if !ok { + log.Errorf("doCreateItem: fail to decode json data %s", data) + return nil, fmt.Errorf("fail to decode json data %s", data) + } var err error generateName, _ := dataDict.GetString("generate_name") @@ -892,22 +907,18 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils return nil, httperrors.NewForbiddenError("Not allow to create item") } - model, err := func() (IModel, error) { - lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId) - defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId) - - return doCreateItem(dispatcher.modelManager, ctx, userCred, ownerProjId, query, data) - }() - + model, err := DoCreate(dispatcher.modelManager, ctx, userCred, query, data, ownerProjId) if err != nil { log.Errorf("fail to doCreateItem %s", err) return nil, httperrors.NewGeneralError(err) } - lockman.LockObject(ctx, model) - defer lockman.ReleaseObject(ctx, model) + func() { + lockman.LockObject(ctx, model) + defer lockman.ReleaseObject(ctx, model) - model.PostCreate(ctx, userCred, ownerProjId, query, data) + model.PostCreate(ctx, userCred, ownerProjId, query, data) + }() OpsLog.LogEvent(model, ACT_CREATE, model.GetShortDesc(ctx), userCred) logclient.AddActionLog(model, logclient.ACT_CREATE, "", userCred, true) @@ -1283,7 +1294,7 @@ func (dispatcher *DBModelDispatcher) Update(ctx context.Context, idStr string, q func DeleteModel(ctx context.Context, userCred mcclient.TokenCredential, item IModel) error { manager := item.GetModelManager() - log.Debugf("Ready to delete %s %s %#v", jsonutils.Marshal(item), item, manager) + // log.Debugf("Ready to delete %s %s %#v", jsonutils.Marshal(item), item, manager) _, err := manager.TableSpec().Update(item, func() error { return item.MarkDelete() }) diff --git a/pkg/cloudcommon/db/db_joint_dispatcher.go b/pkg/cloudcommon/db/db_joint_dispatcher.go index 01b5a41344..fe7ae7caed 100644 --- a/pkg/cloudcommon/db/db_joint_dispatcher.go +++ b/pkg/cloudcommon/db/db_joint_dispatcher.go @@ -126,7 +126,7 @@ func fetchJointItem(dispatcher *DBJointModelDispatcher, ctx context.Context, use if err != nil { return nil, nil, nil, httperrors.NewGeneralError(err) } - item, err := dispatcher.JointModelManager().FetchByIds(master.GetId(), slave.GetId()) + item, err := FetchJointByIds(dispatcher.JointModelManager(), master.GetId(), slave.GetId(), query) if err != nil { return nil, nil, nil, err } diff --git a/pkg/cloudcommon/db/fetch.go b/pkg/cloudcommon/db/fetch.go index e1f398a94f..24f0850ac9 100644 --- a/pkg/cloudcommon/db/fetch.go +++ b/pkg/cloudcommon/db/fetch.go @@ -3,12 +3,48 @@ package db import ( "context" "database/sql" + "fmt" "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/mcclient" ) +func FetchJointByIds(manager IJointModelManager, masterId, slaveId string, query jsonutils.JSONObject) (IJointModel, error) { + obj, err := NewModelObject(manager) + if err != nil { + return nil, err + } + jointObj, ok := obj.(IJointModel) + if !ok { + return nil, fmt.Errorf("FetchByIds not a IJointModel") + } + q := manager.Query() + masterField := queryField(q, manager.GetMasterManager()) + if masterField == nil { + return nil, fmt.Errorf("cannot find master id") + } + slaveField := queryField(q, manager.GetSlaveManager()) + if slaveField == nil { + return nil, fmt.Errorf("cannot find slave id") + } + cond := sqlchemy.AND(sqlchemy.Equals(masterField, masterId), sqlchemy.Equals(slaveField, slaveId)) + q = q.Filter(cond) + q = manager.FilterByParams(q, query) + count := q.Count() + if count > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } else if count == 0 { + return nil, sql.ErrNoRows + } + err = q.First(jointObj) + if err != nil { + return nil, err + } + return jointObj, nil +} + func FetchById(manager IModelManager, idStr string) (IModel, error) { q := manager.Query() q = manager.FilterById(q, idStr) diff --git a/pkg/cloudcommon/db/interface.go b/pkg/cloudcommon/db/interface.go index aca43f4d0f..2d526dc079 100644 --- a/pkg/cloudcommon/db/interface.go +++ b/pkg/cloudcommon/db/interface.go @@ -62,7 +62,7 @@ type IModelManager interface { AllowPerformCheckCreateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool PerformAction(ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) - DoCreate(ctx context.Context, userCred mcclient.TokenCredential, kwargs jsonutils.JSONObject, data jsonutils.JSONObject, realManager IModelManager) (IModel, error) + // DoCreate(ctx context.Context, userCred mcclient.TokenCredential, kwargs jsonutils.JSONObject, data jsonutils.JSONObject, realManager IModelManager) (IModel, error) InitializeData() error @@ -137,7 +137,8 @@ type IJointModelManager interface { GetMasterManager() IStandaloneModelManager GetSlaveManager() IStandaloneModelManager - FetchByIds(masterId string, slaveId string) (IJointModel, error) + // FetchByIds(masterId string, slaveId string, query jsonutils.JSONObject) (IJointModel, error) + FilterByParams(q *sqlchemy.SQuery, params jsonutils.JSONObject) *sqlchemy.SQuery AllowListDescendent(ctx context.Context, userCred mcclient.TokenCredential, model IStandaloneModel, query jsonutils.JSONObject) bool AllowAttach(ctx context.Context, userCred mcclient.TokenCredential, master IStandaloneModel, slave IStandaloneModel) bool diff --git a/pkg/cloudcommon/db/jointbase.go b/pkg/cloudcommon/db/jointbase.go index 844c87d41d..e380befff5 100644 --- a/pkg/cloudcommon/db/jointbase.go +++ b/pkg/cloudcommon/db/jointbase.go @@ -2,7 +2,6 @@ package db import ( "context" - "database/sql" "fmt" "reflect" @@ -69,37 +68,8 @@ func (manager *SJointResourceBaseManager) SlaveField(q *sqlchemy.SQuery) sqlchem return queryField(q, manager.GetSlaveManager()) } -func (manager *SJointResourceBaseManager) FetchByIds(masterId string, slaveId string) (IJointModel, error) { - obj, err := NewModelObject(manager) - if err != nil { - return nil, err - } - jointObj, ok := obj.(IJointModel) - if !ok { - return nil, fmt.Errorf("FetchByIds not a IJointModel") - } - q := manager.Query() - masterField := queryField(q, manager.GetMasterManager()) - if masterField == nil { - return nil, fmt.Errorf("cannot find master id") - } - slaveField := queryField(q, manager.GetSlaveManager()) - if slaveField == nil { - return nil, fmt.Errorf("cannot find slave id") - } - cond := sqlchemy.AND(sqlchemy.Equals(masterField, masterId), sqlchemy.Equals(slaveField, slaveId)) - q = q.Filter(cond) - count := q.Count() - if count > 1 { - return nil, sqlchemy.ErrDuplicateEntry - } else if count == 0 { - return nil, sql.ErrNoRows - } - err = q.First(jointObj) - if err != nil { - return nil, err - } - return jointObj, nil +func (manager *SJointResourceBaseManager) FilterByParams(q *sqlchemy.SQuery, params jsonutils.JSONObject) *sqlchemy.SQuery { + return q } func (manager *SJointResourceBaseManager) AllowListDescendent(ctx context.Context, userCred mcclient.TokenCredential, model IStandaloneModel, query jsonutils.JSONObject) bool { @@ -160,7 +130,7 @@ func JointSlaveID(joint IJointModel) string { // need override func JointMaster(joint IJointModel) IStandaloneModel { // need override masterMan := joint.GetJointModelManager().GetMasterManager() masterId := JointMasterID(joint) - log.Debugf("MasterID: %s %s", masterId, masterMan.KeywordPlural()) + //log.Debugf("MasterID: %s %s", masterId, masterMan.KeywordPlural()) if len(masterId) > 0 { master, _ := masterMan.FetchById(masterId) return master @@ -171,7 +141,7 @@ func JointMaster(joint IJointModel) IStandaloneModel { // need override func JointSlave(joint IJointModel) IStandaloneModel { // need override slaveMan := joint.GetJointModelManager().GetSlaveManager() slaveId := JointSlaveID(joint) - log.Debugf("SlaveID: %s %s", slaveId, slaveMan.KeywordPlural()) + //log.Debugf("SlaveID: %s %s", slaveId, slaveMan.KeywordPlural()) if len(slaveId) > 0 { slave, _ := slaveMan.FetchById(slaveId) return slave diff --git a/pkg/cloudcommon/db/modelbase.go b/pkg/cloudcommon/db/modelbase.go index 7c5501d40a..c48df26c5d 100644 --- a/pkg/cloudcommon/db/modelbase.go +++ b/pkg/cloudcommon/db/modelbase.go @@ -3,7 +3,6 @@ package db import ( "context" "database/sql" - "fmt" "net/http" "yunion.io/x/jsonutils" @@ -162,10 +161,6 @@ func (manager *SModelBaseManager) InitializeData() error { return nil } -func (manager *SModelBaseManager) DoCreate(ctx context.Context, userCred mcclient.TokenCredential, kwargs jsonutils.JSONObject, data jsonutils.JSONObject, realManager IModelManager) (IModel, error) { - return nil, fmt.Errorf("Do create not implement?") -} - func (manager *SModelBaseManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { return q, nil } diff --git a/pkg/cloudcommon/db/opslog.go b/pkg/cloudcommon/db/opslog.go index 18c55604fb..0ffee3eb56 100644 --- a/pkg/cloudcommon/db/opslog.go +++ b/pkg/cloudcommon/db/opslog.go @@ -9,10 +9,12 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/pkg/util/stringutils" "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/cloudcommon/consts" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" ) const ( @@ -107,6 +109,7 @@ const ( ACT_SYNCING_CONF = "syncing_conf" ACT_SYNC_CONF = "sync_conf" ACT_SYNC_CONF_FAIL = "sync_conf_fail" + ACT_SYNC_STATUS = "sync_status" ACT_CHANGE_OWNER = "change_owner" @@ -171,22 +174,23 @@ type SOpsLogManager struct { type SOpsLog struct { SModelBase - Id int64 `primary:"true" auto_increment:"true" list:"user"` // = Column(BigInteger, primary_key=True) - ObjType string `width:"40" charset:"ascii" nullable:"false" list:"user"` // = Column(VARCHAR(40, charset='ascii'), nullable=False) - ObjId string `width:"128" charset:"ascii" nullable:"false" list:"user"` // = Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) - ObjName string `width:"128" charset:"utf8" nullable:"false" list:"user"` //= Column(VARCHAR(128, charset='utf8'), nullable=False) - Action string `width:"32" charset:"ascii" nullable:"false" list:"user"` //= Column(VARCHAR(32, charset='ascii'), nullable=False) - Notes string `width:"2048" charset:"utf8" list:"user"` // = Column(VARCHAR(2048, charset='utf8')) - ProjectId string `name:"tenant_id" width:"128" charset:"ascii" list:"user"` // = Column(VARCHAR(ID_LENGTH, charset='ascii')) - Project string `name:"tenant" width:"128" charset:"utf8" list:"user"` // tenant = Column(VARCHAR(128, charset='utf8')) - UserId string `width:"128" charset:"ascii" list:"user"` // = Column(VARCHAR(ID_LENGTH, charset='ascii')) - User string `width:"128" charset:"utf8" list:"user"` // = Column(VARCHAR(128, charset='utf8')) - DomainId string `width:"128" charset:"ascii" list:"user"` - Domain string `width:"128" charset:"utf8" list:"user"` - Roles string `width:"64" charset:"ascii" list:"user"` // = Column(VARCHAR(64, charset='ascii')) - // billing_type = Column(VARCHAR(64, charset='ascii'), nullable=True) - OpsTime time.Time `nullable:"false" list:"user"` // = Column(DateTime, nullable=False) - OwnerProjectId string `name:"owner_tenant_id" width:"128" charset:"ascii" list:"user"` // = Column(VARCHAR(ID_LENGTH, charset='ascii')) + Id int64 `primary:"true" auto_increment:"true" list:"user"` // = Column(BigInteger, primary_key=True) + ObjType string `width:"40" charset:"ascii" nullable:"false" list:"user" create:"required"` // = Column(VARCHAR(40, charset='ascii'), nullable=False) + ObjId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"` // = Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) + ObjName string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"required"` //= Column(VARCHAR(128, charset='utf8'), nullable=False) + Action string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"required"` //= Column(VARCHAR(32, charset='ascii'), nullable=False) + Notes string `width:"2048" charset:"utf8" list:"user" create:"required"` // = Column(VARCHAR(2048, charset='utf8')) + ProjectId string `name:"tenant_id" width:"128" charset:"ascii" list:"user" create:"required"` // = Column(VARCHAR(ID_LENGTH, charset='ascii')) + Project string `name:"tenant" width:"128" charset:"utf8" list:"user" create:"required"` // tenant = Column(VARCHAR(128, charset='utf8')) + UserId string `width:"128" charset:"ascii" list:"user" create:"required"` // = Column(VARCHAR(ID_LENGTH, charset='ascii')) + User string `width:"128" charset:"utf8" list:"user" create:"required"` // = Column(VARCHAR(128, charset='utf8')) + DomainId string `width:"128" charset:"ascii" list:"user" create:"optional"` + Domain string `width:"128" charset:"utf8" list:"user" create:"optional"` + Roles string `width:"64" charset:"ascii" list:"user" create:"optional"` // = Column(VARCHAR(64, charset='ascii')) + + // BillingType string `width:"64" charset:"ascii" default:"postpaid" list:"user" create:"user"` // billing_type = Column(VARCHAR(64, charset='ascii'), nullable=True) + OpsTime time.Time `nullable:"false" list:"user"` // = Column(DateTime, nullable=False) + OwnerProjectId string `name:"owner_tenant_id" width:"128" charset:"ascii" list:"user" create:"optional"` // = Column(VARCHAR(ID_LENGTH, charset='ascii')) // owner_user_id = Column(VARCHAR(ID_LENGTH, charset='ascii')) } @@ -220,6 +224,9 @@ func (opslog *SOpsLog) GetModelManager() IModelManager { */ func (manager *SOpsLogManager) LogEvent(model IModel, action string, notes interface{}, userCred mcclient.TokenCredential) { + if !consts.OpsLogEnabled() { + return + } if len(model.GetId()) == 0 || len(model.GetName()) == 0 { return } @@ -237,10 +244,11 @@ func (manager *SOpsLogManager) LogEvent(model IModel, action string, notes inter opslog.Domain = userCred.GetDomainName() opslog.Roles = strings.Join(userCred.GetRoles(), ",") opslog.OpsTime = time.Now().UTC() - virtualModel, ok := model.(IVirtualModel) - if ok && virtualModel != nil { + + if virtualModel, ok := model.(IVirtualModel); ok && virtualModel != nil { opslog.OwnerProjectId = virtualModel.GetOwnerProjectId() } + err := manager.TableSpec().Insert(&opslog) if err != nil { log.Errorf("fail to insert opslog: %s", err) @@ -272,13 +280,20 @@ func combineNotes(ctx context.Context, m2 IModel, notes jsonutils.JSONObject) *j } func (manager *SOpsLogManager) logOneJointEvent(ctx context.Context, m1, m2 IModel, event string, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) { - nn := combineNotes(ctx, m2, notes) + nn := notes + if m2 != nil { + nn = combineNotes(ctx, m2, notes) + } manager.LogEvent(m1, event, nn, userCred) } func (manager *SOpsLogManager) logJoinEvent(ctx context.Context, m1, m2 IModel, event string, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) { - manager.logOneJointEvent(ctx, m1, m2, event, userCred, notes) - manager.logOneJointEvent(ctx, m2, m1, event, userCred, notes) + if m1 != nil { + manager.logOneJointEvent(ctx, m1, m2, event, userCred, notes) + } + if m2 != nil { + manager.logOneJointEvent(ctx, m2, m1, event, userCred, notes) + } } func (manager *SOpsLogManager) LogAttachEvent(ctx context.Context, m1, m2 IModel, userCred mcclient.TokenCredential, notes jsonutils.JSONObject) { @@ -290,28 +305,24 @@ func (manager *SOpsLogManager) LogDetachEvent(ctx context.Context, m1, m2 IModel } func (manager *SOpsLogManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - queryDict, ok := query.(*jsonutils.JSONDict) - if !ok { - return nil, fmt.Errorf("invalid query string") - } - objTypes := jsonutils.GetQueryStringArray(queryDict, "obj_type") + objTypes := jsonutils.GetQueryStringArray(query, "obj_type") if objTypes != nil && len(objTypes) > 0 { - queryDict.RemoveIgnoreCase("obj_type") q = q.Filter(sqlchemy.In(q.Field("obj_type"), objTypes)) } - objIds := jsonutils.GetQueryStringArray(queryDict, "obj_id") + objIds := jsonutils.GetQueryStringArray(query, "obj_id") if objIds != nil && len(objIds) > 0 { - queryDict.RemoveIgnoreCase("obj_id") q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("obj_id"), objIds), sqlchemy.In(q.Field("obj_name"), objIds))) } - action := jsonutils.GetQueryStringArray(queryDict, "action") + action := jsonutils.GetQueryStringArray(query, "action") if action != nil && len(action) > 0 { - queryDict.RemoveIgnoreCase("action") q = q.Filter(sqlchemy.In(q.Field("action"), action)) } - if !IsAdminAllowList(userCred, manager) { - q = q.Filter(sqlchemy.OR(sqlchemy.AND(sqlchemy.IsNotNull(q.Field("owner_tenant_id")), sqlchemy.Equals(q.Field("owner_tenant_id"), userCred.GetProjectId())), sqlchemy.Equals(q.Field("tenant_id"), userCred.GetProjectId()))) - } + //if !IsAdminAllowList(userCred, manager) { + // q = q.Filter(sqlchemy.OR( + // sqlchemy.Equals(q.Field("owner_tenant_id"), manager.GetOwnerId(userCred)), + // sqlchemy.Equals(q.Field("tenant_id"), manager.GetOwnerId(userCred)), + // )) + //} since, _ := query.GetTime("since") if !since.IsZero() { q = q.GT("ops_time", since) @@ -326,9 +337,8 @@ func (manager *SOpsLogManager) ListItemFilter(ctx context.Context, q *sqlchemy.S func (manager *SOpsLogManager) SyncOwner(m IModel, former *STenant, userCred mcclient.TokenCredential) { notes := jsonutils.NewDict() notes.Add(jsonutils.NewString(former.GetId()), "former_project_id") - notes.Add(jsonutils.NewString(former.GetName()), "form_project") + notes.Add(jsonutils.NewString(former.GetName()), "former_project") manager.LogEvent(m, ACT_CHANGE_OWNER, notes, userCred) - logclient.AddActionLog(m, logclient.ACT_CHANGE_OWNER, nil, userCred, true) } func (manager *SOpsLogManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { @@ -352,7 +362,7 @@ func (self *SOpsLog) AllowDeleteItem(ctx context.Context, userCred mcclient.Toke } func (self *SOpsLog) ValidateDeleteCondition(ctx context.Context) error { - return fmt.Errorf("forbidden") + return httperrors.NewForbiddenError("not allow to delete log") } func (self *SOpsLogManager) FilterById(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery { @@ -371,8 +381,22 @@ func (self *SOpsLogManager) FilterByName(q *sqlchemy.SQuery, name string) *sqlch func (self *SOpsLogManager) FilterByOwner(q *sqlchemy.SQuery, owner string) *sqlchemy.SQuery { if len(owner) > 0 { - return q.Equals("owner_project_id", owner) - } else { - return q + q = q.Filter(sqlchemy.OR( + sqlchemy.Equals(q.Field("owner_tenant_id"), owner), + sqlchemy.Equals(q.Field("tenant_id"), owner), + )) } + return q +} + +func (manager *SOpsLogManager) GetOwnerId(userCred mcclient.IIdentityProvider) string { + return userCred.GetProjectId() +} + +func (self *SOpsLog) GetOwnerProjectId() string { + return self.OwnerProjectId +} + +func (self *SOpsLog) IsSharable() bool { + return false } diff --git a/pkg/cloudcommon/db/resourcebase.go b/pkg/cloudcommon/db/resourcebase.go index c8ade4dcc9..810dc6d221 100644 --- a/pkg/cloudcommon/db/resourcebase.go +++ b/pkg/cloudcommon/db/resourcebase.go @@ -4,7 +4,6 @@ import ( "context" "time" - "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/sqlchemy" @@ -13,7 +12,7 @@ import ( type SResourceBase struct { SModelBase - CreatedAt time.Time `nullable:"false" created_at:"true" get:"user" list:"user"` + CreatedAt time.Time `nullable:"false" created_at:"true" index:"true" get:"user" list:"user"` UpdatedAt time.Time `nullable:"false" updated_at:"true" list:"user"` UpdateVersion int `default:"0" nullable:"false" auto_version:"true" list:"user"` DeletedAt time.Time `` @@ -36,14 +35,6 @@ func (manager *SResourceBaseManager) RawQuery(fields ...string) *sqlchemy.SQuery return manager.SModelBaseManager.Query(fields...) } -func (manager *SResourceBaseManager) DoCreate(ctx context.Context, userCred mcclient.TokenCredential, kwargs jsonutils.JSONObject, data jsonutils.JSONObject, realManager IModelManager) (IModel, error) { - ownerProjId, err := fetchOwnerProjectId(ctx, manager, userCred, kwargs) - if err != nil { - return nil, err - } - return doCreateItem(realManager, ctx, userCred, ownerProjId, nil, data) -} - func CanDelete(model IModel, ctx context.Context) bool { err := model.ValidateDeleteCondition(ctx) if err == nil { diff --git a/pkg/cloudcommon/db/sharablevirtual.go b/pkg/cloudcommon/db/sharablevirtual.go index 4082fef522..c19791b3ba 100644 --- a/pkg/cloudcommon/db/sharablevirtual.go +++ b/pkg/cloudcommon/db/sharablevirtual.go @@ -11,7 +11,7 @@ import ( type SSharableVirtualResourceBase struct { SVirtualResourceBase - IsPublic bool `default:"false" nullable:"false" index:"true" create:"admin_optional" list:"user"` + IsPublic bool `default:"false" nullable:"false" index:"true" create:"admin_optional" list:"user" update:"admin"` } type SSharableVirtualResourceBaseManager struct { diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index fbba8a4951..dc13c87673 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -131,6 +131,9 @@ func (model *SStandaloneResourceBase) GetShortDesc(ctx context.Context) *jsonuti desc := model.SResourceBase.GetShortDesc(ctx) desc.Add(jsonutils.NewString(model.GetName()), "name") desc.Add(jsonutils.NewString(model.GetId()), "id") + if len(model.ExternalId) > 0 { + desc.Add(jsonutils.NewString(model.ExternalId), "external_id") + } return desc } diff --git a/pkg/cloudcommon/db/tenantcache.go b/pkg/cloudcommon/db/tenantcache.go index c16394a2d6..ee42bc40fc 100644 --- a/pkg/cloudcommon/db/tenantcache.go +++ b/pkg/cloudcommon/db/tenantcache.go @@ -8,9 +8,12 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" + + "yunion.io/x/onecloud/pkg/util/httputils" ) type STenantCacheManager struct { @@ -82,6 +85,9 @@ func (manager *STenantCacheManager) fetchTenantFromKeystone(ctx context.Context, s := auth.GetAdminSession(ctx, consts.GetRegion(), "v1") tenant, err := modules.Projects.Get(s, idStr, nil) if err != nil { + if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 { + return nil, sql.ErrNoRows + } log.Errorf("fetch project fail %s", err) return nil, err } diff --git a/pkg/cloudcommon/db/virtualresource.go b/pkg/cloudcommon/db/virtualresource.go index c058152781..0ec403e7fc 100644 --- a/pkg/cloudcommon/db/virtualresource.go +++ b/pkg/cloudcommon/db/virtualresource.go @@ -14,6 +14,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/logclient" ) type SVirtualResourceBaseManager struct { @@ -232,6 +233,7 @@ func (model *SVirtualResourceBase) PerformChangeOwner(ctx context.Context, userC return nil, err } OpsLog.SyncOwner(model, former, userCred) + logclient.AddActionLog(model, logclient.ACT_CHANGE_OWNER, nil, userCred, true) return nil, nil } diff --git a/pkg/cloudcommon/ethernet/arp/arp.go b/pkg/cloudcommon/ethernet/arp/arp.go new file mode 100644 index 0000000000..29ca322c30 --- /dev/null +++ b/pkg/cloudcommon/ethernet/arp/arp.go @@ -0,0 +1,299 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package arp + +import ( + "encoding/binary" + "errors" + "io" + "net" + + "yunion.io/x/onecloud/pkg/cloudcommon/ethernet" +) + +/* +https://tools.ietf.org/html/rfc826 + +Ethernet transmission layer (not necessarily accessible to the user): + 48.bit: Ethernet address of destination + 48.bit: Ethernet address of sender + 16.bit: Protocol type = ether_type$ADDRESS_RESOLUTION + Ethernet packet data: + 16.bit: (ar$hrd) Hardware address space (e.g., Ethernet, + Packet Radio Net.) + 16.bit: (ar$pro) Protocol address space. For Ethernet + hardware, this is from the set of type + fields ether_typ$. + 8.bit: (ar$hln) byte length of each hardware address + 8.bit: (ar$pln) byte length of each protocol address + 16.bit: (ar$op) opcode (ares_op$REQUEST | ares_op$REPLY) + nbytes: (ar$sha) Hardware address of sender of this + packet, n from the ar$hln field. + mbytes: (ar$spa) Protocol address of sender of this + packet, m from the ar$pln field. + nbytes: (ar$tha) Hardware address of target of this + packet (if known). + mbytes: (ar$tpa) Protocol address of target. + + +Define the following for referring to the values put in the TYPE +field of the Ethernet packet header: + ether_type$XEROX_PUP, + ether_type$DOD_INTERNET, + ether_type$CHAOS, +and a new one: + ether_type$ADDRESS_RESOLUTION. +Also define the following values (to be discussed later): + ares_op$REQUEST (= 1, high byte transmitted first) and + ares_op$REPLY (= 2), +and + ares_hrd$Ethernet (= 1). +*/ +var ( + // ErrInvalidHardwareAddr is returned when one or more invalid hardware + // addresses are passed to NewPacket. + ErrInvalidHardwareAddr = errors.New("invalid hardware address") + + // ErrInvalidIP is returned when one or more invalid IPv4 addresses are + // passed to NewPacket. + ErrInvalidIP = errors.New("invalid IPv4 address") + + // errInvalidARPPacket is returned when an ethernet frame does not + // indicate that an ARP packet is contained in its payload. + errInvalidARPPacket = errors.New("invalid ARP packet") +) + +//go:generate stringer -output=string.go -type=Operation + +// An Operation is an ARP operation, such as request or reply. +type Operation uint16 + +// Operation constants which indicate an ARP request or reply. +const ( + OperationRequest Operation = 1 + OperationReply Operation = 2 +) + +// A Packet is a raw ARP packet, as described in RFC 826. +type Packet struct { + // HardwareType specifies an IANA-assigned hardware type, as described + // in RFC 826. + HardwareType uint16 + + // ProtocolType specifies the internetwork protocol for which the ARP + // request is intended. Typically, this is the IPv4 EtherType. + ProtocolType uint16 + + // HardwareAddrLength specifies the length of the sender and target + // hardware addresses included in a Packet. + HardwareAddrLength uint8 + + // IPLength specifies the length of the sender and target IPv4 addresses + // included in a Packet. + IPLength uint8 + + // Operation specifies the ARP operation being performed, such as request + // or reply. + Operation Operation + + // SenderHardwareAddr specifies the hardware address of the sender of this + // Packet. + SenderHardwareAddr net.HardwareAddr + + // SenderIP specifies the IPv4 address of the sender of this Packet. + SenderIP net.IP + + // TargetHardwareAddr specifies the hardware address of the target of this + // Packet. + TargetHardwareAddr net.HardwareAddr + + // TargetIP specifies the IPv4 address of the target of this Packet. + TargetIP net.IP +} + +// NewPacket creates a new Packet from an input Operation and hardware/IPv4 +// address values for both a sender and target. +// +// If either hardware address is less than 6 bytes in length, or there is a +// length mismatch between the two, ErrInvalidHardwareAddr is returned. +// +// If either IP address is not an IPv4 address, or there is a length mismatch +// between the two, ErrInvalidIP is returned. +func NewPacket(op Operation, srcHW net.HardwareAddr, srcIP net.IP, dstHW net.HardwareAddr, dstIP net.IP) (*Packet, error) { + // Validate hardware addresses for minimum length, and matching length + if len(srcHW) < 6 { + return nil, ErrInvalidHardwareAddr + } + if len(dstHW) < 6 { + return nil, ErrInvalidHardwareAddr + } + if len(srcHW) != len(dstHW) { + return nil, ErrInvalidHardwareAddr + } + + // Validate IP addresses to ensure they are IPv4 addresses, and + // correct length + srcIP = srcIP.To4() + if srcIP == nil { + return nil, ErrInvalidIP + } + dstIP = dstIP.To4() + if dstIP == nil { + return nil, ErrInvalidIP + } + + return &Packet{ + // There is no Go-native way to detect hardware type of a network + // interface, so default to 1 (ethernet 10Mb) for now + HardwareType: 1, + + // Default to EtherType for IPv4 + ProtocolType: uint16(ethernet.EtherTypeIPv4), + + // Populate other fields using input data + HardwareAddrLength: uint8(len(srcHW)), + IPLength: uint8(len(srcIP)), + Operation: op, + SenderHardwareAddr: srcHW, + SenderIP: srcIP, + TargetHardwareAddr: dstHW, + TargetIP: dstIP, + }, nil +} + +// MarshalBinary allocates a byte slice containing the data from a Packet. +// +// MarshalBinary never returns an error. +func (p *Packet) MarshalBinary() ([]byte, error) { + // 2 bytes: hardware type + // 2 bytes: protocol type + // 1 byte : hardware address length + // 1 byte : protocol length + // 2 bytes: operation + // N bytes: source hardware address + // N bytes: source protocol address + // N bytes: target hardware address + // N bytes: target protocol address + + // Though an IPv4 address should always 4 bytes, go-fuzz + // very quickly created several crasher scenarios which + // indicated that these values can lie. + b := make([]byte, 2+2+1+1+2+(p.IPLength*2)+(p.HardwareAddrLength*2)) + + // Marshal fixed length data + + binary.BigEndian.PutUint16(b[0:2], p.HardwareType) + binary.BigEndian.PutUint16(b[2:4], p.ProtocolType) + + b[4] = p.HardwareAddrLength + b[5] = p.IPLength + + binary.BigEndian.PutUint16(b[6:8], uint16(p.Operation)) + + // Marshal variable length data at correct offset using lengths + // defined in p + + n := 8 + hal := int(p.HardwareAddrLength) + pl := int(p.IPLength) + + copy(b[n:n+hal], p.SenderHardwareAddr) + n += hal + + copy(b[n:n+pl], p.SenderIP) + n += pl + + copy(b[n:n+hal], p.TargetHardwareAddr) + n += hal + + copy(b[n:n+pl], p.TargetIP) + + return b, nil +} + +// UnmarshalBinary unmarshals a raw byte slice into a Packet. +func (p *Packet) UnmarshalBinary(b []byte) error { + // Must have enough room to retrieve hardware address and IP lengths + if len(b) < 8 { + return io.ErrUnexpectedEOF + } + + // Retrieve fixed length data + + p.HardwareType = binary.BigEndian.Uint16(b[0:2]) + p.ProtocolType = binary.BigEndian.Uint16(b[2:4]) + + p.HardwareAddrLength = b[4] + p.IPLength = b[5] + + p.Operation = Operation(binary.BigEndian.Uint16(b[6:8])) + + // Unmarshal variable length data at correct offset using lengths + // defined by ml and il + // + // These variables are meant to improve readability of offset calculations + // for the code below + n := 8 + ml := int(p.HardwareAddrLength) + ml2 := ml * 2 + il := int(p.IPLength) + il2 := il * 2 + + // Must have enough room to retrieve both hardware address and IP addresses + addrl := n + ml2 + il2 + if len(b) < addrl { + return io.ErrUnexpectedEOF + } + + // Allocate single byte slice to store address information, which + // is resliced into fields + bb := make([]byte, addrl-n) + + // Sender hardware address + copy(bb[0:ml], b[n:n+ml]) + p.SenderHardwareAddr = bb[0:ml] + n += ml + + // Sender IP address + copy(bb[ml:ml+il], b[n:n+il]) + p.SenderIP = bb[ml : ml+il] + n += il + + // Target hardware address + copy(bb[ml+il:ml2+il], b[n:n+ml]) + p.TargetHardwareAddr = bb[ml+il : ml2+il] + n += ml + + // Target IP address + copy(bb[ml2+il:ml2+il2], b[n:n+il]) + p.TargetIP = bb[ml2+il : ml2+il2] + + return nil +} + +func parsePacket(buf []byte) (*Packet, *ethernet.Frame, error) { + f := new(ethernet.Frame) + if err := f.UnmarshalBinary(buf); err != nil { + return nil, nil, err + } + + // Ignore frames which do not have ARP EtherType + if f.EtherType != ethernet.EtherTypeARP { + return nil, nil, errInvalidARPPacket + } + + p := new(Packet) + if err := p.UnmarshalBinary(f.Payload); err != nil { + return nil, nil, err + } + return p, f, nil +} diff --git a/pkg/cloudcommon/ethernet/arp/client.go b/pkg/cloudcommon/ethernet/arp/client.go new file mode 100644 index 0000000000..88ad0517dc --- /dev/null +++ b/pkg/cloudcommon/ethernet/arp/client.go @@ -0,0 +1,257 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package arp + +import ( + "errors" + "net" + "time" + + "yunion.io/x/onecloud/pkg/cloudcommon/ethernet" +) + +var ( + // errNoIPv4Addr is returned when an interface does not have an IPv4 + // address. + errNoIPv4Addr = errors.New("no IPv4 address available for interface") +) + +// protocolARP is the uint16 EtherType representation of ARP (Address +// Resolution Protocol, RFC 826). +const protocolARP = 0x0806 + +// A Client is an ARP client, which can be used to send and receive +// ARP packets. +type Client struct { + ifi *net.Interface + ip net.IP + p net.PacketConn +} + +// Dial creates a new Client using the specified network interface. +// Dial retrieves the IPv4 address of the interface and binds a raw socket +// to send and receive ARP packets. +func Dial(ifi *net.Interface) (*Client, error) { + // Open raw socket to send and receive ARP packets using ethernet frames + // we build ourselves. + p, err := ethernet.ListenPacket(ifi, protocolARP, nil) + if err != nil { + return nil, err + } + return New(ifi, p) +} + +// New creates a new Client using the specified network interface +// and net.PacketConn. This allows the caller to define exactly how they bind to the +// net.PacketConn. This is most useful to define what protocol to pass to socket(7). +// +// In most cases, callers would be better off calling Dial. +func New(ifi *net.Interface, p net.PacketConn) (*Client, error) { + // Check for usable IPv4 addresses for the Client + addrs, err := ifi.Addrs() + if err != nil { + return nil, err + } + + return newClient(ifi, p, addrs) +} + +// newClient is the internal, generic implementation of newClient. It is used +// to allow an arbitrary net.PacketConn to be used in a Client, so testing +// is easier to accomplish. +func newClient(ifi *net.Interface, p net.PacketConn, addrs []net.Addr) (*Client, error) { + ip, err := firstIPv4Addr(addrs) + if err != nil { + return nil, err + } + + return &Client{ + ifi: ifi, + ip: ip, + p: p, + }, nil +} + +// Close closes the Client's raw socket and stops sending and receiving +// ARP packets. +func (c *Client) Close() error { + return c.p.Close() +} + +// Request sends an ARP request, asking for the hardware address +// associated with an IPv4 address. The response, if any, can be read +// with the Read method. +// +// Unlike Resolve, which provides an easier interface for getting the +// hardware address, Request allows sending many requests in a row, +// retrieving the responses afterwards. +func (c *Client) Request(ip net.IP) error { + if c.ip == nil { + return errNoIPv4Addr + } + + // Create ARP packet for broadcast address to attempt to find the + // hardware address of the input IP address + arp, err := NewPacket(OperationRequest, c.ifi.HardwareAddr, c.ip, ethernet.Broadcast, ip) + if err != nil { + return err + } + return c.WriteTo(arp, ethernet.Broadcast) +} + +// Resolve performs an ARP request, attempting to retrieve the +// hardware address of a machine using its IPv4 address. Resolve must not +// be used concurrently with Read. If you're using Read (usually in a +// loop), you need to use Request instead. Resolve may read more than +// one message if it receives messages unrelated to the request. +func (c *Client) Resolve(ip net.IP) (net.HardwareAddr, error) { + err := c.Request(ip) + if err != nil { + return nil, err + } + + // Loop and wait for replies + for { + arp, _, err := c.Read() + if err != nil { + return nil, err + } + + if arp.Operation != OperationReply || !arp.SenderIP.Equal(ip) { + continue + } + + return arp.SenderHardwareAddr, nil + } +} + +// Read reads a single ARP packet and returns it, together with its +// ethernet frame. +func (c *Client) Read() (*Packet, *ethernet.Frame, error) { + buf := make([]byte, 128) + for { + n, _, err := c.p.ReadFrom(buf) + if err != nil { + return nil, nil, err + } + + p, eth, err := parsePacket(buf[:n]) + if err != nil { + if err == errInvalidARPPacket { + continue + } + return nil, nil, err + } + return p, eth, nil + } +} + +// WriteTo writes a single ARP packet to addr. Note that addr should, +// but doesn't have to, match the target hardware address of the ARP +// packet. +func (c *Client) WriteTo(p *Packet, addr net.HardwareAddr) error { + pb, err := p.MarshalBinary() + if err != nil { + return err + } + + f := ðernet.Frame{ + Destination: p.TargetHardwareAddr, + Source: p.SenderHardwareAddr, + EtherType: ethernet.EtherTypeARP, + Payload: pb, + } + + fb, err := f.MarshalBinary() + if err != nil { + return err + } + + _, err = c.p.WriteTo(fb, ðernet.Addr{HardwareAddr: addr}) + return err +} + +// Reply constructs and sends a reply to an ARP request. On the ARP +// layer, it will be addressed to the sender address of the packet. On +// the ethernet layer, it will be sent to the actual remote address +// from which the request was received. +// +// For more fine-grained control, use WriteTo to write a custom +// response. +func (c *Client) Reply(req *Packet, hwAddr net.HardwareAddr, ip net.IP) error { + p, err := NewPacket(OperationReply, hwAddr, ip, req.SenderHardwareAddr, req.SenderIP) + if err != nil { + return err + } + return c.WriteTo(p, req.SenderHardwareAddr) +} + +// Copyright (c) 2012 The Go Authors. All rights reserved. +// Source code in this file is based on src/net/interface_linux.go, +// from the Go standard library. The Go license can be found here: +// https://golang.org/LICENSE. + +// Documentation taken from net.PacketConn interface. Thanks: +// http://golang.org/pkg/net/#PacketConn. + +// SetDeadline sets the read and write deadlines associated with the +// connection. +func (c *Client) SetDeadline(t time.Time) error { + return c.p.SetDeadline(t) +} + +// SetReadDeadline sets the deadline for future raw socket read calls. +// If the deadline is reached, a raw socket read will fail with a timeout +// (see type net.Error) instead of blocking. +// A zero value for t means a raw socket read will not time out. +func (c *Client) SetReadDeadline(t time.Time) error { + return c.p.SetReadDeadline(t) +} + +// SetWriteDeadline sets the deadline for future raw socket write calls. +// If the deadline is reached, a raw socket write will fail with a timeout +// (see type net.Error) instead of blocking. +// A zero value for t means a raw socket write will not time out. +// Even if a write times out, it may return n > 0, indicating that +// some of the data was successfully written. +func (c *Client) SetWriteDeadline(t time.Time) error { + return c.p.SetWriteDeadline(t) +} + +// HardwareAddr fetches the hardware address for the interface associated +// with the connection. +func (c Client) HardwareAddr() net.HardwareAddr { + return c.ifi.HardwareAddr +} + +// firstIPv4Addr attempts to retrieve the first detected IPv4 address from an +// input slice of network addresses. +func firstIPv4Addr(addrs []net.Addr) (net.IP, error) { + for _, a := range addrs { + if a.Network() != "ip+net" { + continue + } + + ip, _, err := net.ParseCIDR(a.String()) + if err != nil { + return nil, err + } + + // "If ip is not an IPv4 address, To4 returns nil." + // Reference: http://golang.org/pkg/net/#IP.To4 + if ip4 := ip.To4(); ip4 != nil { + return ip4, nil + } + } + + return nil, nil +} diff --git a/pkg/cloudcommon/ethernet/arp/doc.go b/pkg/cloudcommon/ethernet/arp/doc.go new file mode 100644 index 0000000000..0f65a9ccab --- /dev/null +++ b/pkg/cloudcommon/ethernet/arp/doc.go @@ -0,0 +1 @@ +package arp // import "yunion.io/x/onecloud/pkg/cloudcommon/ethernet/arp" diff --git a/pkg/cloudcommon/ethernet/doc.go b/pkg/cloudcommon/ethernet/doc.go new file mode 100644 index 0000000000..ceb0416213 --- /dev/null +++ b/pkg/cloudcommon/ethernet/doc.go @@ -0,0 +1 @@ +package ethernet // import "yunion.io/x/onecloud/pkg/cloudcommon/ethernet" diff --git a/pkg/cloudcommon/ethernet/ethernet.go b/pkg/cloudcommon/ethernet/ethernet.go new file mode 100644 index 0000000000..641bbd8ef4 --- /dev/null +++ b/pkg/cloudcommon/ethernet/ethernet.go @@ -0,0 +1,312 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Package ethernet implements marshaling and unmarshaling of IEEE 802.3 +// Ethernet II frames and IEEE 802.1Q VLAN tags. +package ethernet + +import ( + "encoding/binary" + "errors" + "fmt" + "hash/crc32" + "io" + "net" +) + +//go:generate stringer -output=string.go -type=EtherType + +const ( + // minPayload is the minimum payload size for an Ethernet frame, assuming + // that no 802.1Q VLAN tags are present. + minPayload = 46 +) + +var ( + // Broadcast is a special hardware address which indicates a Frame should + // be sent to every device on a given LAN segment. + Broadcast = net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} +) + +var ( + // ErrInvalidFCS is returned when Frame.UnmarshalFCS detects an incorrect + // Ethernet frame check sequence in a byte slice for a Frame. + ErrInvalidFCS = errors.New("invalid frame check sequence") +) + +// An EtherType is a value used to identify an upper layer protocol +// encapsulated in a Frame. +// +// A list of IANA-assigned EtherType values may be found here: +// http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml. +type EtherType uint16 + +// Common EtherType values frequently used in a Frame. +const ( + EtherTypeIPv4 EtherType = 0x0800 + EtherTypeARP EtherType = 0x0806 + EtherTypeIPv6 EtherType = 0x86DD + + // EtherTypeVLAN and EtherTypeServiceVLAN are used as 802.1Q Tag Protocol + // Identifiers (TPIDs). + EtherTypeVLAN EtherType = 0x8100 + EtherTypeServiceVLAN EtherType = 0x88a8 +) + +// A Frame is an IEEE 802.3 Ethernet II frame. A Frame contains information +// such as source and destination hardware addresses, zero or more optional +// 802.1Q VLAN tags, an EtherType, and payload data. +type Frame struct { + // Destination specifies the destination hardware address for this Frame. + // + // If this address is set to Broadcast, the Frame will be sent to every + // device on a given LAN segment. + Destination net.HardwareAddr + + // Source specifies the source hardware address for this Frame. + // + // Typically, this is the hardware address of the network interface used to + // send this Frame. + Source net.HardwareAddr + + // ServiceVLAN specifies an optional 802.1Q service VLAN tag, for use with + // 802.1ad double tagging, or "Q-in-Q". If ServiceVLAN is not nil, VLAN must + // not be nil as well. + // + // Most users should leave this field set to nil and use VLAN instead. + ServiceVLAN *VLAN + + // VLAN specifies an optional 802.1Q customer VLAN tag, which may or may + // not be present in a Frame. It is important to note that the operating + // system may automatically strip VLAN tags before they can be parsed. + VLAN *VLAN + + // EtherType is a value used to identify an upper layer protocol + // encapsulated in this Frame. + EtherType EtherType + + // Payload is a variable length data payload encapsulated by this Frame. + Payload []byte +} + +// MarshalBinary allocates a byte slice and marshals a Frame into binary form. +func (f *Frame) MarshalBinary() ([]byte, error) { + b := make([]byte, f.length()) + _, err := f.read(b) + return b, err +} + +// MarshalFCS allocates a byte slice, marshals a Frame into binary form, and +// finally calculates and places a 4-byte IEEE CRC32 frame check sequence at +// the end of the slice. +// +// Most users should use MarshalBinary instead. MarshalFCS is provided as a +// convenience for rare occasions when the operating system cannot +// automatically generate a frame check sequence for an Ethernet frame. +func (f *Frame) MarshalFCS() ([]byte, error) { + // Frame length with 4 extra bytes for frame check sequence + b := make([]byte, f.length()+4) + if _, err := f.read(b); err != nil { + return nil, err + } + + // Compute IEEE CRC32 checksum of frame bytes and place it directly + // in the last four bytes of the slice + binary.BigEndian.PutUint32(b[len(b)-4:], crc32.ChecksumIEEE(b[0:len(b)-4])) + return b, nil +} + +// read reads data from a Frame into b. read is used to marshal a Frame +// into binary form, but does not allocate on its own. +func (f *Frame) read(b []byte) (int, error) { + // S-VLAN must also have accompanying C-VLAN. + if f.ServiceVLAN != nil && f.VLAN == nil { + return 0, ErrInvalidVLAN + } + + copy(b[0:6], f.Destination) + copy(b[6:12], f.Source) + + // Marshal each non-nil VLAN tag into bytes, inserting the appropriate + // EtherType/TPID before each, so devices know that one or more VLANs + // are present. + vlans := []struct { + vlan *VLAN + tpid EtherType + }{ + {vlan: f.ServiceVLAN, tpid: EtherTypeServiceVLAN}, + {vlan: f.VLAN, tpid: EtherTypeVLAN}, + } + + n := 12 + for _, vt := range vlans { + if vt.vlan == nil { + continue + } + + // Add VLAN EtherType and VLAN bytes. + binary.BigEndian.PutUint16(b[n:n+2], uint16(vt.tpid)) + if _, err := vt.vlan.read(b[n+2 : n+4]); err != nil { + return 0, err + } + n += 4 + } + + // Marshal actual EtherType after any VLANs, copy payload into + // output bytes. + binary.BigEndian.PutUint16(b[n:n+2], uint16(f.EtherType)) + copy(b[n+2:], f.Payload) + + return len(b), nil +} + +// UnmarshalBinary unmarshals a byte slice into a Frame. +func (f *Frame) UnmarshalBinary(b []byte) error { + // Verify that both hardware addresses and a single EtherType are present + if len(b) < 14 { + return io.ErrUnexpectedEOF + } + + // Track offset in packet for reading data + n := 14 + + // Continue looping and parsing VLAN tags until no more VLAN EtherType + // values are detected + et := EtherType(binary.BigEndian.Uint16(b[n-2 : n])) + switch et { + case EtherTypeServiceVLAN, EtherTypeVLAN: + // VLAN type is hinted for further parsing. An index is returned which + // indicates how many bytes were consumed by VLAN tags. + nn, err := f.unmarshalVLANs(et, b[n:]) + if err != nil { + return err + } + + n += nn + default: + // No VLANs detected. + f.EtherType = et + } + + // Allocate single byte slice to store destination and source hardware + // addresses, and payload + bb := make([]byte, 6+6+len(b[n:])) + copy(bb[0:6], b[0:6]) + f.Destination = bb[0:6] + copy(bb[6:12], b[6:12]) + f.Source = bb[6:12] + + // There used to be a minimum payload length restriction here, but as + // long as two hardware addresses and an EtherType are present, it + // doesn't really matter what is contained in the payload. We will + // follow the "robustness principle". + copy(bb[12:], b[n:]) + f.Payload = bb[12:] + + return nil +} + +// UnmarshalFCS computes the IEEE CRC32 frame check sequence of a Frame, +// verifies it against the checksum present in the byte slice, and finally, +// unmarshals a byte slice into a Frame. +// +// Most users should use UnmarshalBinary instead. UnmarshalFCS is provided as +// a convenience for rare occasions when the operating system cannot +// automatically verify a frame check sequence for an Ethernet frame. +func (f *Frame) UnmarshalFCS(b []byte) error { + // Must contain enough data for FCS, to avoid panics + if len(b) < 4 { + return io.ErrUnexpectedEOF + } + + // Verify checksum in slice versus newly computed checksum + want := binary.BigEndian.Uint32(b[len(b)-4:]) + got := crc32.ChecksumIEEE(b[0 : len(b)-4]) + if want != got { + return ErrInvalidFCS + } + + return f.UnmarshalBinary(b[0 : len(b)-4]) +} + +// length calculates the number of bytes required to store a Frame. +func (f *Frame) length() int { + // If payload is less than the required minimum length, we zero-pad up to + // the required minimum length + pl := len(f.Payload) + if pl < minPayload { + pl = minPayload + } + + // Add additional length if VLAN tags are needed. + var vlanLen int + switch { + case f.ServiceVLAN != nil && f.VLAN != nil: + vlanLen = 8 + case f.VLAN != nil: + vlanLen = 4 + } + + // 6 bytes: destination hardware address + // 6 bytes: source hardware address + // N bytes: VLAN tags (if present) + // 2 bytes: EtherType + // N bytes: payload length (may be padded) + return 6 + 6 + vlanLen + 2 + pl +} + +// unmarshalVLANs unmarshals S/C-VLAN tags. It is assumed that tpid +// is a valid S/C-VLAN TPID. +func (f *Frame) unmarshalVLANs(tpid EtherType, b []byte) (int, error) { + // 4 or more bytes must remain for valid S/C-VLAN tag and EtherType. + if len(b) < 4 { + return 0, io.ErrUnexpectedEOF + } + + // Track how many bytes are consumed by VLAN tags. + var n int + + switch tpid { + case EtherTypeServiceVLAN: + vlan := new(VLAN) + if err := vlan.UnmarshalBinary(b[n : n+2]); err != nil { + return 0, err + } + f.ServiceVLAN = vlan + + // Assume that a C-VLAN immediately trails an S-VLAN. + if EtherType(binary.BigEndian.Uint16(b[n+2:n+4])) != EtherTypeVLAN { + return 0, ErrInvalidVLAN + } + + // 4 or more bytes must remain for valid C-VLAN tag and EtherType. + n += 4 + if len(b[n:]) < 4 { + return 0, io.ErrUnexpectedEOF + } + + // Continue to parse the C-VLAN. + fallthrough + case EtherTypeVLAN: + vlan := new(VLAN) + if err := vlan.UnmarshalBinary(b[n : n+2]); err != nil { + return 0, err + } + + f.VLAN = vlan + f.EtherType = EtherType(binary.BigEndian.Uint16(b[n+2 : n+4])) + n += 4 + default: + panic(fmt.Sprintf("unknown VLAN TPID: %04x", tpid)) + } + + return n, nil +} diff --git a/pkg/cloudcommon/ethernet/raw.go b/pkg/cloudcommon/ethernet/raw.go new file mode 100644 index 0000000000..2d19553615 --- /dev/null +++ b/pkg/cloudcommon/ethernet/raw.go @@ -0,0 +1,201 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package ethernet + +// Package raw enables reading and writing data at the device driver level for +// a network interface. + +import ( + "errors" + "net" + "time" + + "golang.org/x/net/bpf" +) + +const ( + // Maximum read timeout per syscall. + // It is required because read/recvfrom won't be interrupted on closing of the file descriptor. + readTimeout = 200 * time.Millisecond +) + +var ( + // ErrNotImplemented is returned when certain functionality is not yet + // implemented for the host operating system. + ErrNotImplemented = errors.New("raw: not implemented") +) + +var _ net.Addr = &Addr{} + +// Addr is a network address which can be used to contact other machines, using +// their hardware addresses. +type Addr struct { + HardwareAddr net.HardwareAddr +} + +// Network returns the address's network name, "raw". +func (a *Addr) Network() string { + return "raw" +} + +// String returns the address's hardware address. +func (a *Addr) String() string { + return a.HardwareAddr.String() +} + +var _ net.PacketConn = &Conn{} + +// Conn is an implementation of the net.PacketConn interface which can send +// and receive data at the network interface device driver level. +type Conn struct { + // packetConn is the operating system-specific implementation of + // a raw connection. + p *packetConn +} + +// ReadFrom implements the net.PacketConn ReadFrom method. +func (c *Conn) ReadFrom(b []byte) (int, net.Addr, error) { + return c.p.ReadFrom(b) +} + +// WriteTo implements the net.PacketConn WriteTo method. +func (c *Conn) WriteTo(b []byte, addr net.Addr) (int, error) { + return c.p.WriteTo(b, addr) +} + +// Close closes the connection. +func (c *Conn) Close() error { + return c.p.Close() +} + +// LocalAddr returns the local network address. +func (c *Conn) LocalAddr() net.Addr { + return c.p.LocalAddr() +} + +// SetDeadline implements the net.PacketConn SetDeadline method. +func (c *Conn) SetDeadline(t time.Time) error { + return c.p.SetDeadline(t) +} + +// SetReadDeadline implements the net.PacketConn SetReadDeadline method. +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.p.SetReadDeadline(t) +} + +// SetWriteDeadline implements the net.PacketConn SetWriteDeadline method. +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.p.SetWriteDeadline(t) +} + +var _ bpf.Setter = &Conn{} + +// SetBPF attaches an assembled BPF program to the connection. +func (c *Conn) SetBPF(filter []bpf.RawInstruction) error { + return c.p.SetBPF(filter) +} + +// SetPromiscuous enables or disables promiscuous mode on the interface, allowing it +// to receive traffic that is not addressed to the interface. +func (c *Conn) SetPromiscuous(b bool) error { + return c.p.SetPromiscuous(b) +} + +// Stats contains statistics about a Conn. +type Stats struct { + // The total number of packets received. + Packets uint64 + + // The number of packets dropped. + Drops uint64 +} + +// Stats retrieves statistics from the Conn. +// +// Only supported on Linux at this time. +func (c *Conn) Stats() (*Stats, error) { + return c.p.Stats() +} + +// ListenPacket creates a net.PacketConn which can be used to send and receive +// data at the network interface device driver level. +// +// ifi specifies the network interface which will be used to send and receive +// data. +// +// proto specifies the protocol (usually the EtherType) which should be +// captured and transmitted. proto, if needed, is automatically converted to +// network byte order (big endian), akin to the htons() function in C. +// +// cfg specifies optional configuration which may be operating system-specific. +// A nil Config is equivalent to the default configuration: send and receive +// data at the network interface device driver level (usually raw Ethernet frames). +func ListenPacket(ifi *net.Interface, proto uint16, cfg *Config) (*Conn, error) { + // A nil config is an empty Config. + if cfg == nil { + cfg = &Config{} + } + + p, err := listenPacket(ifi, proto, *cfg) + if err != nil { + return nil, err + } + + return &Conn{ + p: p, + }, nil +} + +// A Config can be used to specify additional options for a Conn. +type Config struct { + // Linux only: call socket(7) with SOCK_DGRAM instead of SOCK_RAW. + // Has no effect on other operating systems. + LinuxSockDGRAM bool + + // Experimental: Linux only (for now, but can be ported to BSD): + // disables repeated socket reads due to internal timeouts, at the expense + // of losing the ability to cancel a ReadFrom operation by calling the Close + // method of the net.PacketConn. + // + // Not recommended for programs which may need to open and close multiple + // sockets during program runs. This may save some CPU time by avoiding a + // busy loop for programs which do not need timeouts, or programs which keep + // a single socket open for the entire duration of the program. + NoTimeouts bool + + // Linux only: do not accumulate packet socket statistic counters. Packet + // socket statistics are reset on each call to retrieve them via getsockopt, + // but this package's default behavior is to continue accumulating the + // statistics internally per Conn. To use the Linux default behavior of + // resetting statistics on each call to Stats, set this value to true. + NoCumulativeStats bool +} + +// htons converts a short (uint16) from host-to-network byte order. +// Thanks to mikioh for this neat trick: +// https://github.com/mikioh/-stdyng/blob/master/afpacket.go +func htons(i uint16) uint16 { + return (i<<8)&0xff00 | i>>8 +} + +// Copyright (c) 2012 The Go Authors. All rights reserved. +// Source code in this file is based on src/net/interface_linux.go, +// from the Go standard library. The Go license can be found here: +// https://golang.org/LICENSE. + +// Taken from: +// https://github.com/golang/go/blob/master/src/net/net.go#L417-L421. +type timeoutError struct{} + +func (e *timeoutError) Error() string { return "i/o timeout" } +func (e *timeoutError) Timeout() bool { return true } +func (e *timeoutError) Temporary() bool { return true } diff --git a/pkg/cloudcommon/ethernet/raw_bsd.go b/pkg/cloudcommon/ethernet/raw_bsd.go new file mode 100644 index 0000000000..c79f8a1f1e --- /dev/null +++ b/pkg/cloudcommon/ethernet/raw_bsd.go @@ -0,0 +1,389 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// +build darwin dragonfly freebsd netbsd openbsd + +package ethernet + +import ( + "errors" + "fmt" + "net" + "os" + "runtime" + "sync" + "syscall" + "time" + "unsafe" + + "golang.org/x/net/bpf" +) + +const ( + // bpfDIn tells BPF to pass through only incoming packets, so we do not + // receive the packets we send using BPF. + bpfDIn = 0 + + // osFreeBSD is the GOOS name for FreeBSD. + osFreeBSD = "freebsd" +) + +// bpfLen returns the length of the BPF header prepended to each incoming ethernet +// frame. FreeBSD uses a slightly modified header from other BSD variants. +func bpfLen() int { + // Majority of BSD family systems use the bpf_hdr struct, but FreeBSD + // has replaced this with bpf_xhdr, which is longer. + const ( + bpfHeaderLen = 18 + bpfXHeaderLen = 26 + ) + + if runtime.GOOS == osFreeBSD { + return bpfXHeaderLen + } + + return bpfHeaderLen +} + +var ( + // Must implement net.PacketConn at compile-time. + _ net.PacketConn = &packetConn{} +) + +// packetConn is the Linux-specific implementation of net.PacketConn for this +// package. +type packetConn struct { + proto uint16 + ifi *net.Interface + f *os.File + fd int + buflen int + + // Timeouts set via Set{Read,}Deadline, guarded by mutex + timeoutMu sync.RWMutex + rtimeout time.Time +} + +// listenPacket creates a net.PacketConn which can be used to send and receive +// data at the device driver level. +func listenPacket(ifi *net.Interface, proto uint16, _ Config) (*packetConn, error) { + // Config is, as of now, unused on BSD. + // TODO(mdlayher): consider porting NoTimeouts option to BSD if it pans out. + + var f *os.File + var err error + + // Try to find an available BPF device + for i := 0; i <= 10; i++ { + bpfPath := fmt.Sprintf("/dev/bpf%d", i) + f, err = os.OpenFile(bpfPath, os.O_RDWR, 0666) + if err == nil { + // Found a usable device + break + } + + // Device is busy, try the next one + if perr, ok := err.(*os.PathError); ok { + if perr.Err.(syscall.Errno) == syscall.EBUSY { + continue + } + } + + return nil, err + } + + if f == nil { + return nil, errors.New("unable to open BPF device") + } + + fd := int(f.Fd()) + if fd == -1 { + return nil, errors.New("unable to open BPF device") + } + + // Configure BPF device to send and receive data + buflen, err := configureBPF(fd, ifi, proto) + if err != nil { + return nil, err + } + + return &packetConn{ + proto: proto, + ifi: ifi, + f: f, + fd: fd, + buflen: buflen, + }, nil +} + +// ReadFrom implements the net.PacketConn.ReadFrom method. +func (p *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { + p.timeoutMu.Lock() + deadline := p.rtimeout + p.timeoutMu.Unlock() + + buf := make([]byte, p.buflen) + var n int + + for { + var timeout time.Duration + + if deadline.IsZero() { + timeout = readTimeout + } else { + timeout = deadline.Sub(time.Now()) + if timeout > readTimeout { + timeout = readTimeout + } + } + + tv, err := newTimeval(timeout) + if err != nil { + return 0, nil, err + } + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(p.fd), syscall.BIOCSRTIMEOUT, uintptr(unsafe.Pointer(tv))); err != 0 { + return 0, nil, syscall.Errno(err) + } + + // Attempt to receive on socket + // The read sycall will NOT be interrupted by closing of the socket + n, err = syscall.Read(p.fd, buf) + if err != nil { + return n, nil, err + } + if n > 0 { + break + } + } + + // TODO(mdlayher): consider parsing BPF header if it proves useful. + // BPF header length depends on the platform this code is running on + bpfl := bpfLen() + + // Retrieve source MAC address of ethernet header + mac := make(net.HardwareAddr, 6) + copy(mac, buf[bpfl+6:bpfl+12]) + + // Skip past BPF header to retrieve ethernet frame + out := copy(b, buf[bpfl:bpfl+n]) + + return out, &Addr{ + HardwareAddr: mac, + }, nil +} + +// WriteTo implements the net.PacketConn.WriteTo method. +func (p *packetConn) WriteTo(b []byte, _ net.Addr) (int, error) { + return syscall.Write(p.fd, b) +} + +// Close closes the connection. +func (p *packetConn) Close() error { + return p.f.Close() +} + +// LocalAddr returns the local network address. +func (p *packetConn) LocalAddr() net.Addr { + return &Addr{ + HardwareAddr: p.ifi.HardwareAddr, + } +} + +// SetDeadline implements the net.PacketConn.SetDeadline method. +func (p *packetConn) SetDeadline(t time.Time) error { + return p.SetReadDeadline(t) +} + +// SetReadDeadline implements the net.PacketConn.SetReadDeadline method. +func (p *packetConn) SetReadDeadline(t time.Time) error { + p.timeoutMu.Lock() + p.rtimeout = t + p.timeoutMu.Unlock() + return nil +} + +// SetWriteDeadline implements the net.PacketConn.SetWriteDeadline method. +func (p *packetConn) SetWriteDeadline(t time.Time) error { + return ErrNotImplemented +} + +// SetBPF attaches an assembled BPF program to a raw net.PacketConn. +func (p *packetConn) SetBPF(filter []bpf.RawInstruction) error { + // Base filter filters traffic based on EtherType + base, err := bpf.Assemble(baseFilter(p.proto)) + if err != nil { + return err + } + + // Append user filter to base filter, translate to raw format, + // and apply to BPF device + return syscall.SetBpf(p.fd, assembleBpfInsn(append(base, filter...))) +} + +// SetPromiscuous enables or disables promiscuous mode on the interface, allowing it +// to receive traffic that is not addressed to the interface. +func (p *packetConn) SetPromiscuous(b bool) error { + m := 1 + if !b { + m = 0 + } + + return syscall.SetBpfPromisc(p.fd, m) +} + +// Stats retrieves statistics from the Conn. +func (p *packetConn) Stats() (*Stats, error) { + return nil, ErrNotImplemented +} + +// configureBPF configures a BPF device with the specified file descriptor to +// use the specified network and interface and protocol. +func configureBPF(fd int, ifi *net.Interface, proto uint16) (int, error) { + // Use specified interface with BPF device + if err := syscall.SetBpfInterface(fd, ifi.Name); err != nil { + return 0, err + } + + // Inform BPF to send us its data immediately + if err := syscall.SetBpfImmediate(fd, 1); err != nil { + return 0, err + } + + // Check buffer size of BPF device + buflen, err := syscall.BpfBuflen(fd) + if err != nil { + return 0, err + } + + // Do not automatically complete source address in ethernet headers + if err := syscall.SetBpfHeadercmpl(fd, 1); err != nil { + return 0, err + } + + // Only retrieve incoming traffic using BPF device + if err := setBPFDirection(fd, bpfDIn); err != nil { + return 0, err + } + + // Build and apply base BPF filter which checks for correct EtherType + // on incoming packets + prog, err := bpf.Assemble(baseInterfaceFilter(proto, ifi.MTU)) + if err != nil { + return 0, err + } + if err := syscall.SetBpf(fd, assembleBpfInsn(prog)); err != nil { + return 0, err + } + + // Flush any packets currently in the BPF device's buffer + if err := syscall.FlushBpf(fd); err != nil { + return 0, err + } + + return buflen, nil +} + +// setBPFDirection enables filtering traffic traveling in a specific direction +// using BPF, so that traffic sent by this package is not captured when reading +// using this package. +func setBPFDirection(fd int, direction int) error { + _, _, err := syscall.Syscall( + syscall.SYS_IOCTL, + uintptr(fd), + // Even though BIOCSDIRECTION is preferred on FreeBSD, BIOCSSEESENT continues + // to work, and is required for other BSD platforms + syscall.BIOCSSEESENT, + uintptr(unsafe.Pointer(&direction)), + ) + if err != 0 { + return syscall.Errno(err) + } + + return nil +} + +// assembleBpfInsn assembles a slice of bpf.RawInstructions to the format required by +// package syscall. +func assembleBpfInsn(filter []bpf.RawInstruction) []syscall.BpfInsn { + // Copy each bpf.RawInstruction into syscall.BpfInsn. If needed, + // the structures have the same memory layout and could probably be + // unsafely cast to each other for speed. + insns := make([]syscall.BpfInsn, 0, len(filter)) + for _, ins := range filter { + insns = append(insns, syscall.BpfInsn{ + Code: ins.Op, + Jt: ins.Jt, + Jf: ins.Jf, + K: ins.K, + }) + } + + return insns +} + +// baseInterfaceFilter creates a base BPF filter which filters traffic based +// on its EtherType and returns up to "mtu" bytes of data for processing. +func baseInterfaceFilter(proto uint16, mtu int) []bpf.Instruction { + return append( + // Filter traffic based on EtherType + baseFilter(proto), + // Accept the packet bytes up to the interface's MTU + bpf.RetConstant{ + Val: uint32(mtu), + }, + ) +} + +// baseFilter creates a base BPF filter which filters traffic based on its +// EtherType. baseFilter can be prepended to other filters to handle common +// filtering tasks. +func baseFilter(proto uint16) []bpf.Instruction { + // Offset | Length | Comment + // ------------------------- + // 00 | 06 | Ethernet destination MAC address + // 06 | 06 | Ethernet source MAC address + // 12 | 02 | Ethernet EtherType + const ( + etherTypeOffset = 12 + etherTypeLength = 2 + ) + + return []bpf.Instruction{ + // Load EtherType value from Ethernet header + bpf.LoadAbsolute{ + Off: etherTypeOffset, + Size: etherTypeLength, + }, + // If EtherType is equal to the protocol we are using, jump to instructions + // added outside of this function. + bpf.JumpIf{ + Cond: bpf.JumpEqual, + Val: uint32(proto), + SkipTrue: 1, + }, + // EtherType does not match our protocol + bpf.RetConstant{ + Val: 0, + }, + } +} + +// newTimeval transforms a duration into a syscall.Timeval struct. +// An error is returned in case of zero time value. +func newTimeval(timeout time.Duration) (*syscall.Timeval, error) { + if timeout < time.Microsecond { + return nil, &timeoutError{} + } + return &syscall.Timeval{ + Sec: int64(timeout / time.Second), + Usec: int32(timeout % time.Second / time.Microsecond), + }, nil +} diff --git a/pkg/cloudcommon/ethernet/raw_linux.go b/pkg/cloudcommon/ethernet/raw_linux.go new file mode 100644 index 0000000000..11c5edf413 --- /dev/null +++ b/pkg/cloudcommon/ethernet/raw_linux.go @@ -0,0 +1,361 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package ethernet + +import ( + "net" + "os" + "sync" + "sync/atomic" + "time" + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" +) + +var ( + // Must implement net.PacketConn at compile-time. + _ net.PacketConn = &packetConn{} +) + +// packetConn is the Linux-specific implementation of net.PacketConn for this +// package. +type packetConn struct { + ifi *net.Interface + s socket + pbe uint16 + + // Should timeouts be set at all? + noTimeouts bool + + // Should stats be accumulated instead of reset on each call? + noCumulativeStats bool + + // Internal storage for cumulative stats. + stats Stats + + // Timeouts set via Set{Read,}Deadline, guarded by mutex. + timeoutMu sync.RWMutex + rtimeout time.Time +} + +// socket is an interface which enables swapping out socket syscalls for +// testing. +type socket interface { + Bind(unix.Sockaddr) error + Close() error + FD() int + GetSockopt(level, name int, v unsafe.Pointer, l uintptr) error + Recvfrom([]byte, int) (int, unix.Sockaddr, error) + Sendto([]byte, int, unix.Sockaddr) error + SetSockopt(level, name int, v unsafe.Pointer, l uint32) error + SetTimeout(time.Duration) error +} + +// listenPacket creates a net.PacketConn which can be used to send and receive +// data at the device driver level. +func listenPacket(ifi *net.Interface, proto uint16, cfg Config) (*packetConn, error) { + // Convert proto to big endian. + pbe := htons(proto) + + // Enabling overriding the socket type via config. + typ := unix.SOCK_RAW + if cfg.LinuxSockDGRAM { + typ = unix.SOCK_DGRAM + } + + // Open a packet socket using specified socket and protocol types. + sock, err := unix.Socket(unix.AF_PACKET, typ, int(pbe)) + if err != nil { + return nil, err + } + + // Wrap raw socket in socket interface. + pc, err := newPacketConn(ifi, &sysSocket{fd: sock}, pbe) + if err != nil { + return nil, err + } + + pc.noTimeouts = cfg.NoTimeouts + pc.noCumulativeStats = cfg.NoCumulativeStats + return pc, nil +} + +// newPacketConn creates a net.PacketConn using the specified network +// interface, wrapped socket and big endian protocol number. +// +// It is the entry point for tests in this package. +func newPacketConn(ifi *net.Interface, s socket, pbe uint16) (*packetConn, error) { + // Bind the packet socket to the interface specified by ifi + // packet(7): + // Only the sll_protocol and the sll_ifindex address fields are used for + // purposes of binding. + err := s.Bind(&unix.SockaddrLinklayer{ + Protocol: pbe, + Ifindex: ifi.Index, + }) + if err != nil { + return nil, err + } + + return &packetConn{ + ifi: ifi, + s: s, + pbe: pbe, + }, nil +} + +// ReadFrom implements the net.PacketConn.ReadFrom method. +func (p *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { + p.timeoutMu.Lock() + deadline := p.rtimeout + p.timeoutMu.Unlock() + + var ( + // Information returned by unix.Recvfrom. + n int + addr unix.Sockaddr + err error + + // Timeout for a single loop iteration. + timeout = readTimeout + ) + + for { + if !deadline.IsZero() { + timeout = deadline.Sub(time.Now()) + if timeout > readTimeout { + timeout = readTimeout + } + } + + // Set a timeout for this iteration if configured to do so. + if !p.noTimeouts { + if err := p.s.SetTimeout(timeout); err != nil { + return 0, nil, err + } + } + + // Attempt to receive on socket + // The recvfrom sycall will NOT be interrupted by closing of the socket + n, addr, err = p.s.Recvfrom(b, 0) + switch err { + case nil: + // Got data, break this loop shortly. + case unix.EAGAIN: + // Hit a timeout, keep looping. + continue + default: + // Return on any other error. + return n, nil, err + } + + // Got data, exit the loop. + break + } + + // Retrieve hardware address and other information from addr. + sa, ok := addr.(*unix.SockaddrLinklayer) + if !ok || sa.Halen < 6 { + return n, nil, unix.EINVAL + } + + // Use length specified to convert byte array into a hardware address slice. + mac := make(net.HardwareAddr, sa.Halen) + copy(mac, sa.Addr[:]) + + // packet(7): + // sll_hatype and sll_pkttype are set on received packets for your + // information. + // TODO(mdlayher): determine if similar fields exist and are useful on + // non-Linux platforms + return n, &Addr{ + HardwareAddr: mac, + }, nil +} + +// WriteTo implements the net.PacketConn.WriteTo method. +func (p *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) { + // Ensure correct Addr type. + a, ok := addr.(*Addr) + if !ok || a.HardwareAddr == nil || len(a.HardwareAddr) < 6 { + return 0, unix.EINVAL + } + + // Convert hardware address back to byte array form. + var baddr [8]byte + copy(baddr[:], a.HardwareAddr) + + // Send message on socket to the specified hardware address from addr + // packet(7): + // When you send packets it is enough to specify sll_family, sll_addr, + // sll_halen, sll_ifindex, and sll_protocol. The other fields should + // be 0. + // In this case, sll_family is taken care of automatically by unix. + err := p.s.Sendto(b, 0, &unix.SockaddrLinklayer{ + Ifindex: p.ifi.Index, + Halen: uint8(len(a.HardwareAddr)), + Addr: baddr, + Protocol: p.pbe, + }) + return len(b), err +} + +// Close closes the connection. +func (p *packetConn) Close() error { + return p.s.Close() +} + +// LocalAddr returns the local network address. +func (p *packetConn) LocalAddr() net.Addr { + return &Addr{ + HardwareAddr: p.ifi.HardwareAddr, + } +} + +// SetDeadline implements the net.PacketConn.SetDeadline method. +func (p *packetConn) SetDeadline(t time.Time) error { + return p.SetReadDeadline(t) +} + +// SetReadDeadline implements the net.PacketConn.SetReadDeadline method. +func (p *packetConn) SetReadDeadline(t time.Time) error { + p.timeoutMu.Lock() + p.rtimeout = t + p.timeoutMu.Unlock() + return nil +} + +// SetWriteDeadline implements the net.PacketConn.SetWriteDeadline method. +func (p *packetConn) SetWriteDeadline(t time.Time) error { + return nil +} + +// SetBPF attaches an assembled BPF program to a raw net.PacketConn. +func (p *packetConn) SetBPF(filter []bpf.RawInstruction) error { + prog := unix.SockFprog{ + Len: uint16(len(filter)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])), + } + + err := p.s.SetSockopt( + unix.SOL_SOCKET, + unix.SO_ATTACH_FILTER, + unsafe.Pointer(&prog), + uint32(unsafe.Sizeof(prog)), + ) + if err != nil { + return os.NewSyscallError("setsockopt", err) + } + + return nil +} + +// SetPromiscuous enables or disables promiscuous mode on the interface, allowing it +// to receive traffic that is not addressed to the interface. +func (p *packetConn) SetPromiscuous(b bool) error { + mreq := unix.PacketMreq{ + Ifindex: int32(p.ifi.Index), + Type: unix.PACKET_MR_PROMISC, + } + + membership := unix.PACKET_ADD_MEMBERSHIP + if !b { + membership = unix.PACKET_DROP_MEMBERSHIP + } + + return p.s.SetSockopt(unix.SOL_PACKET, membership, unsafe.Pointer(&mreq), unix.SizeofPacketMreq) +} + +// Stats retrieves statistics from the Conn. +func (p *packetConn) Stats() (*Stats, error) { + var s unix.TpacketStats + if err := p.s.GetSockopt(unix.SOL_PACKET, unix.PACKET_STATISTICS, unsafe.Pointer(&s), unsafe.Sizeof(s)); err != nil { + return nil, err + } + + return p.handleStats(s), nil +} + +// handleStats handles creation of Stats structures from raw packet socket stats. +func (p *packetConn) handleStats(s unix.TpacketStats) *Stats { + // Does the caller want instantaneous stats as provided by Linux? If so, + // return the structure directly. + if p.noCumulativeStats { + return &Stats{ + Packets: uint64(s.Packets), + Drops: uint64(s.Drops), + } + } + + // The caller wants cumulative stats. Add stats with the internal stats + // structure and return a copy of the resulting stats. + packets := atomic.AddUint64(&p.stats.Packets, uint64(s.Packets)) + drops := atomic.AddUint64(&p.stats.Drops, uint64(s.Drops)) + + return &Stats{ + Packets: packets, + Drops: drops, + } +} + +// sysSocket is the default socket implementation. It makes use of +// Linux-specific system calls to handle raw socket functionality. +type sysSocket struct { + fd int +} + +// Method implementations simply invoke the syscall of the same name, but pass +// the file descriptor stored in the sysSocket as the socket to use. +func (s *sysSocket) Bind(sa unix.Sockaddr) error { return unix.Bind(s.fd, sa) } +func (s *sysSocket) Close() error { return unix.Close(s.fd) } +func (s *sysSocket) FD() int { return s.fd } +func (s *sysSocket) GetSockopt(level, name int, v unsafe.Pointer, l uintptr) error { + _, _, err := unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(s.fd), uintptr(level), uintptr(name), uintptr(v), uintptr(unsafe.Pointer(&l)), 0) + if err != 0 { + return unix.Errno(err) + } + return nil +} +func (s *sysSocket) Recvfrom(p []byte, flags int) (int, unix.Sockaddr, error) { + return unix.Recvfrom(s.fd, p, flags) +} +func (s *sysSocket) Sendto(p []byte, flags int, to unix.Sockaddr) error { + return unix.Sendto(s.fd, p, flags, to) +} +func (s *sysSocket) SetSockopt(level, name int, v unsafe.Pointer, l uint32) error { + _, _, err := unix.Syscall6(unix.SYS_SETSOCKOPT, uintptr(s.fd), uintptr(level), uintptr(name), uintptr(v), uintptr(l), 0) + if err != 0 { + return unix.Errno(err) + } + return nil +} +func (s *sysSocket) SetTimeout(timeout time.Duration) error { + tv, err := newTimeval(timeout) + if err != nil { + return err + } + return unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, tv) +} + +// newTimeval transforms a duration into a unix.Timeval struct. +// An error is returned in case of zero time value. +func newTimeval(timeout time.Duration) (*unix.Timeval, error) { + if timeout < time.Microsecond { + return nil, &timeoutError{} + } + return &unix.Timeval{ + Sec: int64(timeout / time.Second), + Usec: int64(timeout % time.Second / time.Microsecond), + }, nil +} diff --git a/pkg/cloudcommon/ethernet/vlan.go b/pkg/cloudcommon/ethernet/vlan.go new file mode 100644 index 0000000000..11e7bbb669 --- /dev/null +++ b/pkg/cloudcommon/ethernet/vlan.go @@ -0,0 +1,140 @@ +// MIT License +// =========== + +// Copyright (C) 2015 Matt Layher + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package ethernet + +import ( + "encoding/binary" + "errors" + "io" +) + +const ( + // VLANNone is a special VLAN ID which indicates that no VLAN is being + // used in a Frame. In this case, the VLAN's other fields may be used + // to indicate a Frame's priority. + VLANNone = 0x000 + + // VLANMax is a reserved VLAN ID which may indicate a wildcard in some + // management systems, but may not be configured or transmitted in a + // VLAN tag. + VLANMax = 0xfff +) + +var ( + // ErrInvalidVLAN is returned when a VLAN tag is invalid due to one of the + // following reasons: + // - Priority of greater than 7 is detected + // - ID of greater than 4094 (0xffe) is detected + // - A customer VLAN does not follow a service VLAN (when using Q-in-Q) + ErrInvalidVLAN = errors.New("invalid VLAN") +) + +// Priority is an IEEE P802.1p priority level. Priority can be any value from +// 0 to 7. +// +// It is important to note that priority 1 (PriorityBackground) actually has +// a lower priority than 0 (PriorityBestEffort). All other Priority constants +// indicate higher priority as the integer values increase. +type Priority uint8 + +// IEEE P802.1p recommended priority levels. Note that PriorityBackground has +// a lower priority than PriorityBestEffort. +const ( + PriorityBackground Priority = 1 + PriorityBestEffort Priority = 0 + PriorityExcellentEffort Priority = 2 + PriorityCriticalApplications Priority = 3 + PriorityVideo Priority = 4 + PriorityVoice Priority = 5 + PriorityInternetworkControl Priority = 6 + PriorityNetworkControl Priority = 7 +) + +// A VLAN is an IEEE 802.1Q Virtual LAN (VLAN) tag. A VLAN contains +// information regarding traffic priority and a VLAN identifier for +// a given Frame. +type VLAN struct { + // Priority specifies a IEEE P802.1p priority level. Priority can be any + // value from 0 to 7. + Priority Priority + + // DropEligible indicates if a Frame is eligible to be dropped in the + // presence of network congestion. + DropEligible bool + + // ID specifies the VLAN ID for a Frame. ID can be any value from 0 to + // 4094 (0x000 to 0xffe), allowing up to 4094 VLANs. + // + // If ID is 0 (0x000, VLANNone), no VLAN is specified, and the other fields + // simply indicate a Frame's priority. + ID uint16 +} + +// MarshalBinary allocates a byte slice and marshals a VLAN into binary form. +func (v *VLAN) MarshalBinary() ([]byte, error) { + b := make([]byte, 2) + _, err := v.read(b) + return b, err +} + +// read reads data from a VLAN into b. read is used to marshal a VLAN into +// binary form, but does not allocate on its own. +func (v *VLAN) read(b []byte) (int, error) { + // Check for VLAN priority in valid range + if v.Priority > PriorityNetworkControl { + return 0, ErrInvalidVLAN + } + + // Check for VLAN ID in valid range + if v.ID >= VLANMax { + return 0, ErrInvalidVLAN + } + + // 3 bits: priority + ub := uint16(v.Priority) << 13 + + // 1 bit: drop eligible + var drop uint16 + if v.DropEligible { + drop = 1 + } + ub |= drop << 12 + + // 12 bits: VLAN ID + ub |= v.ID + + binary.BigEndian.PutUint16(b, ub) + return 2, nil +} + +// UnmarshalBinary unmarshals a byte slice into a VLAN. +func (v *VLAN) UnmarshalBinary(b []byte) error { + // VLAN tag is always 2 bytes + if len(b) != 2 { + return io.ErrUnexpectedEOF + } + + // 3 bits: priority + // 1 bit : drop eligible + // 12 bits: VLAN ID + ub := binary.BigEndian.Uint16(b[0:2]) + v.Priority = Priority(uint8(ub >> 13)) + v.DropEligible = ub&0x1000 != 0 + v.ID = ub & 0x0fff + + // Check for VLAN ID in valid range + if v.ID >= VLANMax { + return ErrInvalidVLAN + } + + return nil +} diff --git a/pkg/cloudcommon/notifyclient/events.go b/pkg/cloudcommon/notifyclient/events.go new file mode 100644 index 0000000000..56453696b5 --- /dev/null +++ b/pkg/cloudcommon/notifyclient/events.go @@ -0,0 +1,13 @@ +package notifyclient + +const ( + SYSTEM_ERROR = "SYSTEM_ERROR" + SYSTEM_WARNING = "SYSTEM_WARNING" + + SERVER_CREATED = "SERVER_CREATED" + SERVER_CREATED_ADMIN = "SERVER_CREATED_ADMIN" + SERVER_DELETED = "SERVER_DELETED" + SERVER_DELETED_ADMIN = "SERVER_DELETED_ADMIN" + SERVER_REBUILD_ROOT = "SERVER_REBUILD_ROOT" + SERVER_CHANGE_FLAVOR = "SERVER_CHANGE_FLAVOR" +) diff --git a/pkg/cloudcommon/notifyclient/notify.go b/pkg/cloudcommon/notifyclient/notify.go index f0c189dd08..a2b95a7367 100644 --- a/pkg/cloudcommon/notifyclient/notify.go +++ b/pkg/cloudcommon/notifyclient/notify.go @@ -1,35 +1,158 @@ package notifyclient import ( + "context" + "fmt" + "html/template" + "io/ioutil" + "path/filepath" + "strings" + "yunion.io/x/jsonutils" "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon/consts" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" ) -const ( - PRIORITY_IMPORTANT = "important" - PRIORITY_CRITICAL = "fatal" - PRIORITY_NORMAL = "normal" - - SERVER_CREATED = "SERVER_CREATED" - SERVER_CREATED_ADMIN = "SERVER_CREATED_ADMIN" - SERVER_DELETED = "SERVER_DELETED" - SERVER_DELETED_ADMIN = "SERVER_DELETED_ADMIN" - SERVER_REBUILD_ROOT = "SERVER_REBUILD_ROOT" - SERVER_CHANGE_FLAVOR = "SERVER_CHANGE_FLAVOR" +var ( + templatesTable map[string]*template.Template + notifyClientWorkerMan *appsrv.SWorkerManager ) -var templateDir string - -func SetTemplateDir(dir string) { - templateDir = dir +func init() { + notifyClientWorkerMan = appsrv.NewWorkerManager("NotifyClientWorkerManager", 1, 50, false) + templatesTable = make(map[string]*template.Template) } -func NotifySystemError(id string, name string, status string, reason string) error { - log.Errorf("ID: %s Name %s Status %s REASON %s", id, name, status, reason) - return nil +func getTemplateString(topic string, contType string, channel notify.TNotifyChannel) ([]byte, error) { + if len(channel) > 0 { + path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), contType, fmt.Sprintf("%s.%s", topic, string(channel))) + cont, err := ioutil.ReadFile(path) + if err == nil { + return cont, nil + } + } + path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), contType, topic) + return ioutil.ReadFile(path) } -func Notify(to string, event string, priority string, data jsonutils.JSONObject) error { - log.Infof("notify %s event %s priority %s data %s", to, event, priority, data) - return nil +func getTemplate(topic string, contType string) (*template.Template, error) { + key := fmt.Sprintf("%s.%s", topic, contType) + if _, ok := templatesTable[key]; !ok { + cont, err := getTemplateString(topic, contType, "") + if err != nil { + return nil, err + } + tmp, err := template.New(key).Parse(string(cont)) + if err != nil { + return nil, err + } + templatesTable[key] = tmp + } + return templatesTable[key], nil +} + +func getContent(topic string, contType string, data jsonutils.JSONObject) (string, error) { + tmpl, err := getTemplate(topic, contType) + if err != nil { + return "", err + } + buf := strings.Builder{} + err = tmpl.Execute(&buf, data.Interface()) + if err != nil { + return "", err + } + // log.Debugf("notify.getContent %s %s %s %s", topic, contType, data, buf.String()) + return buf.String(), nil +} + +func Notify(recipientId string, isGroup bool, priority notify.TNotifyPriority, event string, data jsonutils.JSONObject) { + switch priority { + case notify.NotifyPriorityCritical: + NotifyCritical(recipientId, isGroup, event, data) + case notify.NotifyPriorityImportant: + NotifyImportant(recipientId, isGroup, event, data) + default: + NotifyNormal(recipientId, isGroup, event, data) + } +} + +func RawNotify(recipientId string, isGroup bool, channels []notify.TNotifyChannel, priority notify.TNotifyPriority, event string, data jsonutils.JSONObject) { + log.Infof("notify %s event %s priority %s", recipientId, event, priority) + msg := notify.SNotifyMessage{} + if isGroup { + msg.Gid = recipientId + } else { + msg.Uid = recipientId + } + msg.Priority = priority + msg.ContactType = channels + topic, _ := getContent(event, "title", data) + if len(topic) == 0 { + topic = event + } + msg.Topic = topic + body, _ := getContent(event, "content", data) + if len(body) == 0 { + body = data.String() + } + msg.Msg = body + // log.Debugf("send notification %s %s", topic, body) + notifyClientWorkerMan.Run(func() { + s := auth.GetAdminSession(context.Background(), consts.GetRegion(), "") + notify.Notifications.Send(s, msg) + }, nil, nil) +} + +func NotifyNormal(recipientId string, isGroup bool, event string, data jsonutils.JSONObject) { + RawNotify(recipientId, isGroup, + []notify.TNotifyChannel{notify.NotifyByEmail, notify.NotifyByDingTalk}, + notify.NotifyPriorityNormal, + event, data) +} + +func NotifyImportant(recipientId string, isGroup bool, event string, data jsonutils.JSONObject) { + RawNotify(recipientId, isGroup, + []notify.TNotifyChannel{notify.NotifyByEmail, notify.NotifyByDingTalk, notify.NotifyByMobile}, + notify.NotifyPriorityImportant, + event, data) +} + +func NotifyCritical(recipientId string, isGroup bool, event string, data jsonutils.JSONObject) { + RawNotify(recipientId, isGroup, + []notify.TNotifyChannel{notify.NotifyByEmail, notify.NotifyByDingTalk, notify.NotifyByMobile}, + notify.NotifyPriorityCritical, + event, data) +} + +func SystemNotify(event string, data jsonutils.JSONObject) { + NotifyCritical(auth.AdminCredential().GetProjectId(), true, event, data) +} + +func NotifyGeneralSystemError(data jsonutils.JSONObject) { + SystemNotify(SYSTEM_ERROR, data) +} + +type sSystemErrorMsg struct { + Id string + Name string + Event string + Reason string +} + +func NotifySystemError(idstr string, name string, event string, reason string) { + msg := sSystemErrorMsg{ + Id: idstr, + Name: name, + Event: event, + Reason: reason, + } + SystemNotify(SYSTEM_ERROR, jsonutils.Marshal(msg)) +} + +func NotifySystemWarning(data jsonutils.JSONObject) { + SystemNotify(SYSTEM_WARNING, data) } diff --git a/pkg/cloudcommon/notifyclient/notify_test.go b/pkg/cloudcommon/notifyclient/notify_test.go new file mode 100644 index 0000000000..318566411b --- /dev/null +++ b/pkg/cloudcommon/notifyclient/notify_test.go @@ -0,0 +1,63 @@ +package notifyclient + +import ( + "html/template" + "strings" + "testing" + + "yunion.io/x/jsonutils" +) + +func TestNotifyTemplate(t *testing.T) { + cases := []struct { + template string + data interface{} + want string + }{ + { + `云主机{{ .name }}创建成功`, + struct { + Name string + }{ + Name: "testsrv-1", + }, + `云主机testsrv-1创建成功`, + }, + { + `您的云主机{{ .name }}已经创建成功,服务器IP地址为{{ .ips }},{{ if .account }}初始帐号为{{ .account }},{{ end }}{{ if .keypair }}访问密钥为{{ .keypair }},{{ end }}{{ if len .password }}初始密码为{{ .password }},{{ end }}请使用{{ if .windows }}远程桌面连接器(RDC){{ else }}SSH{{ end }}或控制面板控制台访问云主机。`, + struct { + Name string + Ips string + Account string + Keypair string + Password string + Windows bool + }{ + Name: "testsrv-1", + Ips: "10.168.222.23", + Account: "root", + Password: "1234567", + Windows: false, + }, + `您的云主机testsrv-1已经创建成功,服务器IP地址为10.168.222.23,初始帐号为root,初始密码为1234567,请使用SSH或控制面板控制台访问云主机。`, + }, + } + for _, c := range cases { + temp, err := template.New("template").Parse(c.template) + if err != nil { + t.Errorf("parse template %s fail %s", c.template, err) + } else { + strBuild := strings.Builder{} + jsonData := jsonutils.Marshal(c.data) + t.Logf("jsonData: %s", jsonData) + err = temp.Execute(&strBuild, jsonData.Interface()) + if err != nil { + t.Error("execute template fail %s", err) + } else { + if strBuild.String() != c.want { + t.Error("fail: got %s want %s", strBuild.String(), c.want) + } + } + } + } +} diff --git a/pkg/cloudcommon/options.go b/pkg/cloudcommon/options.go index e050237639..a539bc8034 100644 --- a/pkg/cloudcommon/options.go +++ b/pkg/cloudcommon/options.go @@ -39,7 +39,7 @@ type CommonOptions struct { NotifyAdminUser string `default:"sysadmin" help:"System administrator user ID or name to notify"` EnableSsl bool `help:"Enable https"` - SslCafile string `help:"ssl certificate ca root file, separating ca and cert file is not encouraged" alias:"ca-file"` + SslCaCerts string `help:"ssl certificate ca root file, separating ca and cert file is not encouraged" alias:"ca-file"` SslCertfile string `help:"ssl certification file, normally combines all the certificates in the chain" alias:"cert-file"` SslKeyfile string `help:"ssl certification private key file" alias:"key-file"` @@ -56,7 +56,7 @@ type DBOptions struct { AutoSyncTable bool `help:"Automatically synchronize table changes if differences are detected"` GlobalVirtualResourceNamespace bool `help:"Per project namespace or global namespace for virtual resources"` - DebugSqlchemy bool `default:"False" help:"Print SQL executed by sqlchemy"` + DebugSqlchemy bool `default:"false" help:"Print SQL executed by sqlchemy"` } func (this *DBOptions) GetDBConnection() (dialect, connstr string, err error) { @@ -80,7 +80,7 @@ func ParseOptions(optStruct interface{}, args []string, configFileName string, s log.Fatalf("Error define argument parser: %v", err) } - err = parser.ParseArgs(args[1:], false) + err = parser.ParseArgs2(args[1:], false, false) if err != nil { log.Fatalf("Parse arguments error: %v", err) } @@ -121,6 +121,8 @@ func ParseOptions(optStruct interface{}, args []string, configFileName string, s } } + parser.SetDefault() + if len(optionsRef.ApplicationID) == 0 { optionsRef.ApplicationID = serviceName } diff --git a/pkg/cloudcommon/policy/defaults.go b/pkg/cloudcommon/policy/defaults.go index 3257705881..85378b27b2 100644 --- a/pkg/cloudcommon/policy/defaults.go +++ b/pkg/cloudcommon/policy/defaults.go @@ -63,6 +63,18 @@ var ( Action: PolicyActionGet, Result: rbacutils.UserAllow, }, + { + Service: "compute", + Resource: "cachedimages", + Action: PolicyActionList, + Result: rbacutils.UserAllow, + }, + { + Service: "compute", + Resource: "cachedimages", + Action: PolicyActionGet, + Result: rbacutils.UserAllow, + }, { // quotas for any services // Service: "compute", @@ -132,5 +144,17 @@ var ( Extra: []string{"update-torrent-status"}, Result: rbacutils.GuestAllow, }, + { + Service: "log", + Resource: "actions", + Action: PolicyActionList, + Result: rbacutils.OwnerAllow, + }, + { + Service: "log", + Resource: "actions", + Action: PolicyActionGet, + Result: rbacutils.OwnerAllow, + }, } ) diff --git a/pkg/cloudcommon/policy/policy.go b/pkg/cloudcommon/policy/policy.go index bafcf379f5..13c5aa4c7d 100644 --- a/pkg/cloudcommon/policy/policy.go +++ b/pkg/cloudcommon/policy/policy.go @@ -251,7 +251,7 @@ func (manager *SPolicyManager) allowWithoutCache(isAdmin bool, userCred mcclient } } if consts.IsRbacDebug() { - log.Debugf("[RBAC: %v] %s %s %s %#v permission %s", isAdmin, service, resource, action, extra, currentPriv) + log.Debugf("[RBAC: %v] %s %s %s %#v permission %s userCred: %s", isAdmin, service, resource, action, extra, currentPriv, userCredJson) } return unifyRbacResult(isAdmin, currentPriv) } diff --git a/pkg/cloudcommon/policy/resources.go b/pkg/cloudcommon/policy/resources.go index d5b6ab532f..9484d28225 100644 --- a/pkg/cloudcommon/policy/resources.go +++ b/pkg/cloudcommon/policy/resources.go @@ -9,6 +9,7 @@ var ( "storages", "wires", "vpcs", + "networks", "cloudregions", "recyclebins", "schedtags", diff --git a/pkg/cloudcommon/service/doc.go b/pkg/cloudcommon/service/doc.go new file mode 100644 index 0000000000..bc80eaec8d --- /dev/null +++ b/pkg/cloudcommon/service/doc.go @@ -0,0 +1 @@ +package service // import "yunion.io/x/onecloud/pkg/cloudcommon/service" diff --git a/pkg/cloudcommon/service/services.go b/pkg/cloudcommon/service/services.go new file mode 100644 index 0000000000..e6d29208d6 --- /dev/null +++ b/pkg/cloudcommon/service/services.go @@ -0,0 +1,3 @@ +package service + +type SServiceBase struct{} diff --git a/pkg/cloudcommon/sshkeys/doc.go b/pkg/cloudcommon/sshkeys/doc.go new file mode 100644 index 0000000000..a050fcff73 --- /dev/null +++ b/pkg/cloudcommon/sshkeys/doc.go @@ -0,0 +1 @@ +package sshkeys // import "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" diff --git a/pkg/cloudcommon/sshkeys/sshkeys.go b/pkg/cloudcommon/sshkeys/sshkeys.go new file mode 100644 index 0000000000..8bd4c4b697 --- /dev/null +++ b/pkg/cloudcommon/sshkeys/sshkeys.go @@ -0,0 +1,19 @@ +package sshkeys + +import "yunion.io/x/jsonutils" + +type SSHKeys struct { + PublicKey string + DeletePublicKey string + AdminPublicKey string + ProjectPublicKey string +} + +func GetKeys(data jsonutils.JSONObject) *SSHKeys { + var ret = new(SSHKeys) + ret.PublicKey, _ = data.GetString("public_key") + ret.DeletePublicKey, _ = data.GetString("delete_public_key") + ret.AdminPublicKey, _ = data.GetString("admin_public_key") + ret.ProjectPublicKey, _ = data.GetString("project_public_key") + return ret +} diff --git a/pkg/cloudcommon/storagetypes/doc.go b/pkg/cloudcommon/storagetypes/doc.go new file mode 100644 index 0000000000..2619223c2a --- /dev/null +++ b/pkg/cloudcommon/storagetypes/doc.go @@ -0,0 +1 @@ +package storagetypes // import "yunion.io/x/onecloud/pkg/cloudcommon/storagetypes" diff --git a/pkg/cloudcommon/storagetypes/storagetypes.go b/pkg/cloudcommon/storagetypes/storagetypes.go new file mode 100644 index 0000000000..4779914227 --- /dev/null +++ b/pkg/cloudcommon/storagetypes/storagetypes.go @@ -0,0 +1,18 @@ +package storagetypes + +// TODO: move models/storages.go storage types to this file +var ( + STORAGE_LOCAL = "local" + STORAGE_BAREMETAL = "baremetal" + STORAGE_SHEEPDOG = "sheepdog" + STORAGE_RBD = "rbd" + STORAGE_DOCKER = "docker" + STORAGE_NAS = "nas" + STORAGE_VSAN = "vsan" + STORAGE_NFS = "nfs" + + DISK_TYPE_ROTATE = "rotate" + DISK_TYPE_SSD = "ssd" + + Local = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS} +) diff --git a/pkg/cloudcommon/types/doc.go b/pkg/cloudcommon/types/doc.go new file mode 100644 index 0000000000..27095d71b3 --- /dev/null +++ b/pkg/cloudcommon/types/doc.go @@ -0,0 +1 @@ +package types // import "yunion.io/x/onecloud/pkg/cloudcommon/types" diff --git a/pkg/cloudcommon/types/ipmi.go b/pkg/cloudcommon/types/ipmi.go new file mode 100644 index 0000000000..62728cad43 --- /dev/null +++ b/pkg/cloudcommon/types/ipmi.go @@ -0,0 +1,32 @@ +package types + +import "yunion.io/x/jsonutils" + +const ( + POWER_STATUS_ON = "on" + POWER_STATUS_OFF = "off" +) + +type SIPMIInfo struct { + Username string `json:"username"` + Password string `json:"password"` + IpAddr string `json:"ip_addr"` + Present bool `json:"present"` + LanChannel int `json:"lan_channel"` +} + +func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject { + data := jsonutils.NewDict() + if info.Username != "" { + data.Add(jsonutils.NewString(info.Username), "ipmi_username") + } + if info.Password != "" { + data.Add(jsonutils.NewString(info.Password), "ipmi_password") + } + if info.IpAddr != "" { + data.Add(jsonutils.NewString(info.IpAddr), "ipmi_ip_addr") + } + data.Add(jsonutils.NewBool(info.Present), "ipmi_present") + data.Add(jsonutils.NewInt(int64(info.LanChannel)), "ipmi_lan_channel") + return data +} diff --git a/pkg/cloudcommon/types/network.go b/pkg/cloudcommon/types/network.go new file mode 100644 index 0000000000..1890c357d8 --- /dev/null +++ b/pkg/cloudcommon/types/network.go @@ -0,0 +1,19 @@ +package types + +type SNetworkConfig struct { + GuestDhcp string `json:"guest_dhcp"` + GuestGateway string `json:"guest_gateway"` + GuestIpStart string `json:"guest_ip_start"` + GuestIpEnd string `json:"guest_ip_end"` + GuestIpMask int `json:"guest_ip_mask"` + Id string `json:"id"` + IsEmulated bool `json:"is_emulated"` + IsPublic bool `json:"is_public"` + IsSystem bool `json:"is_system"` + Name string `json:"name"` + ServerType string `json:"server_type"` + Status string `json:"status"` + ProjectId string `json:"tenant_id"` + VlanId int `json:"vlan_id"` + WireId string `json:"wire_id"` +} diff --git a/pkg/cloudcommon/types/nic.go b/pkg/cloudcommon/types/nic.go new file mode 100644 index 0000000000..086a4c9215 --- /dev/null +++ b/pkg/cloudcommon/types/nic.go @@ -0,0 +1,95 @@ +package types + +import ( + "net" + + "yunion.io/x/pkg/util/netutils" +) + +const ( + NIC_TYPE_IPMI = "ipmi" + NIC_TYPE_ADMIN = "admin" +) + +var ( + NIC_TYPES = []string{NIC_TYPE_IPMI, NIC_TYPE_ADMIN} +) + +type SNic struct { + Type string `json:"nic_type"` + Domain string `json:"domain"` + Wire string `json:"wire"` + IpAddr string `json:"ip_addr"` + WireId string `json:"wire_id"` + NetId string `json:"net_id"` + Rate int64 `json:"rate"` + Mtu int64 `json:"mtu"` + Mac string `json:"mac"` + Dns string `json:"dns"` + MaskLen int8 `json:"masklen"` + Net string `json:"net"` + Gateway string `json:"gateway"` + LinkUp bool `json:"link_up"` + Routes []SRoute `json:"routes,omitempty"` +} + +func (n SNic) GetNetMask() string { + return netutils.Masklen2Mask(n.MaskLen).String() +} + +func (n SNic) GetMac() net.HardwareAddr { + return getMac(n.Mac) +} + +type SRoute []string + +type SServerNic struct { + Index int `json:"index"` + Bridge string `json:"bridge"` + Domain string `json:"domain"` + Ip string `json:"ip"` + Vlan int `json:"vlan"` + Driver string `json:"driver"` + Masklen int `json:"masklen"` + Virtual bool `json:"virtual"` + Manual bool `json:"manual"` + WireId string `json:"wire_id"` + NetId string `json:"net_id"` + Mac string `json:"mac"` + BandWidth int `json:"bw"` + Dns string `json:"dns"` + Net string `json:"net"` + Interface string `json:"interface"` + Gateway string `json:"gateway"` + Ifname string `json:"ifname"` + Routes []SRoute `json:"routes,omitempty"` + NicType string `json:"nic_type,omitempty"` + LinkUp bool `json:"link_up,omitempty"` + Mtu int64 `json:"mtu,omitempty"` +} + +func (n SServerNic) GetNetMask() string { + return netutils.Masklen2Mask(int8(n.Masklen)).String() +} + +func (n SServerNic) GetMac() net.HardwareAddr { + return getMac(n.Mac) +} + +func (n SServerNic) ToNic() SNic { + return SNic{ + Type: n.NicType, + Domain: n.Domain, + IpAddr: n.Ip, + WireId: n.WireId, + NetId: n.NetId, + Mac: n.Mac, + Dns: n.Dns, + MaskLen: int8(n.Masklen), + Net: n.Net, + Gateway: n.Gateway, + Routes: n.Routes, + LinkUp: n.LinkUp, + Mtu: n.Mtu, + } +} diff --git a/pkg/cloudcommon/types/types.go b/pkg/cloudcommon/types/types.go new file mode 100644 index 0000000000..44427cb54e --- /dev/null +++ b/pkg/cloudcommon/types/types.go @@ -0,0 +1,86 @@ +package types + +import "net" + +type SSHConfig struct { + Username string `json:"username,omitempty"` + RemoteIP string `json:"ip"` + Password string `json:"password"` +} + +type SDMISystemInfo struct { + Manufacture string `json:"manufacture"` + Model string `json:"model"` + Version string `json:"version,omitempty"` + SN string `json:"sn"` +} + +func (info *SDMISystemInfo) ToIPMISystemInfo() *SIPMISystemInfo { + return &SIPMISystemInfo{ + Manufacture: info.Manufacture, + Model: info.Model, + Version: info.Version, + SN: info.SN, + } +} + +type SCPUInfo struct { + Count int `json:"count"` + Model string `json:"desc"` + Freq int `json:"freq"` + Cache int `json:"cache"` +} + +type SDMICPUInfo struct { + Nodes int `json:"nodes"` +} + +type SDMIMemInfo struct { + Total int `json:"total"` +} + +type SNicDevInfo struct { + Dev string `json:"dev"` + Mac net.HardwareAddr `json:"mac"` + Speed int `json:"speed"` + Up bool `json:"up"` + Mtu int `json:"mtu"` +} + +func getMac(macStr string) net.HardwareAddr { + mac, _ := net.ParseMAC(macStr) + return mac +} + +type SDiskInfo struct { + Dev string `json:"dev"` + Sector int64 `json:"sector"` + Block int64 `json:"block"` + Size int64 `json:"size"` + Rotate bool `json:"rotate"` + ModuleInfo string `json:"module"` + Kernel string `json:"kernel"` + PCIClass string `json:"pci_class"` + Driver string `json:"driver"` +} + +type SIPMISystemInfo struct { + Manufacture string `json:"manufacture"` + Model string `json:"model"` + SN string `json:"sn"` + Version string `json:"version"` + BSN string `json:"bsn"` +} + +type SIPMILanConfig struct { + IPSrc string `json:"ipsrc"` + IPAddr string `json:"ipaddr"` + Netmask string `json:"netmask"` + Mac net.HardwareAddr `json:"mac"` + Gateway string `json:"gateway"` +} + +type SIPMIBootFlags struct { + Dev string `json:"dev"` + Sol *bool `json:"sol"` +} diff --git a/pkg/cloudcommon/version/doc.go b/pkg/cloudcommon/version/doc.go deleted file mode 100644 index 315032e76c..0000000000 --- a/pkg/cloudcommon/version/doc.go +++ /dev/null @@ -1 +0,0 @@ -package version // import "yunion.io/x/onecloud/pkg/cloudcommon/version" diff --git a/pkg/cloudcommon/version/version.go b/pkg/cloudcommon/version/version.go deleted file mode 100644 index d42061b73a..0000000000 --- a/pkg/cloudcommon/version/version.go +++ /dev/null @@ -1,46 +0,0 @@ -package version - -import ( - "strconv" - "strings" -) - -func less(v1Str, v2Str string) (bool, bool) { - v1 := strings.Split(v1Str, ".") - v2 := strings.Split(v2Str, ".") - var i = 0 - for ; i < len(v2); i++ { - if i >= len(v1) { - return true, false - } - v, _ := strconv.ParseInt(v2[i], 10, 0) - compareV, _ := strconv.ParseInt(v1[i], 10, 0) - if v < compareV { - return false, false - } else if compareV < v { - return true, false - } - } - if i < len(v1)-1 { - return false, false - } - return true, true -} - -func LE(v1Str, v2Str string) bool { - l, _ := less(v1Str, v2Str) - return l -} - -func LT(v1Str, v2Str string) bool { - l, e := less(v1Str, v2Str) - return l && !e -} - -func GT(v1Str, v2Str string) bool { - return LT(v2Str, v1Str) -} - -func GE(v1Str, v2Str string) bool { - return LE(v2Str, v1Str) -} diff --git a/pkg/cloudcommon/workmanager/doc.go b/pkg/cloudcommon/workmanager/doc.go new file mode 100644 index 0000000000..47d3ea42a4 --- /dev/null +++ b/pkg/cloudcommon/workmanager/doc.go @@ -0,0 +1 @@ +package workmanager // import "yunion.io/x/onecloud/pkg/cloudcommon/workmanager" diff --git a/pkg/cloudcommon/workmanager/manager.go b/pkg/cloudcommon/workmanager/manager.go new file mode 100644 index 0000000000..b50735b1df --- /dev/null +++ b/pkg/cloudcommon/workmanager/manager.go @@ -0,0 +1,114 @@ +package workmanager + +import ( + "context" + "runtime/debug" + "sync/atomic" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appctx" +) + +type DelayTaskFunc func(context.Context, interface{}) (jsonutils.JSONObject, error) +type OnTaskFailed func(context.Context, string) +type OnTaskCompleted func(context.Context, jsonutils.JSONObject) + +type SWorkManager struct { + curCount int32 + + onFailed OnTaskFailed + onCompleted OnTaskCompleted +} + +func (w *SWorkManager) add() { + atomic.AddInt32(&w.curCount, 1) +} + +func (w *SWorkManager) done() { + atomic.AddInt32(&w.curCount, -1) +} + +// If delay task is not panic and task func return err is nil +// task complete will be called, otherwise called task failed +// Params is interface for receive any type, task func should do type assertion +func (w *SWorkManager) DelayTask(ctx context.Context, task DelayTaskFunc, params interface{}) { + if ctx == nil || ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID) == nil { + w.DelayTaskWithoutReqctx(ctx, task, params) + return + } else { + w.add() + go func() { + defer w.done() + defer func() { + if r := recover(); r != nil { + log.Errorf("DelayTask panic: %s", r) + debug.PrintStack() + switch val := r.(type) { + case string: + w.onFailed(ctx, val) + case error: + w.onFailed(ctx, val.Error()) + default: + w.onFailed(ctx, "Unknown panic") + } + } + }() + + // HACK: callback only + if task == nil { + w.onCompleted(ctx, nil) + return + } + + res, err := task(ctx, params) + if err != nil { + log.Infof("DelayTask failed: %s", err) + w.onFailed(ctx, err.Error()) + } else { + log.Infof("DelayTask complete: %v", res) + w.onCompleted(ctx, res) + } + }() + } +} + +// response task by self, did not callback +func (w *SWorkManager) DelayTaskWithoutReqctx(ctx context.Context, task DelayTaskFunc, params interface{}) { + w.add() + go func() { + defer w.done() + defer func() { + if r := recover(); r != nil { + log.Errorln("DelayTaskWithoutReqctx panic: ", r) + debug.PrintStack() + } + }() + + if task == nil { + return + } + + if _, err := task(ctx, params); err != nil { + log.Errorln("DelayTaskWithoutReqctx error: ", err) + w.onFailed(ctx, err.Error()) + } + }() +} + +func (w *SWorkManager) Stop() { + log.Infof("WorkManager stop, waitting for workers ...") + for w.curCount > 0 { + log.Warningf("Busy workers count %d, waiting stopped", w.curCount) + time.Sleep(1 * time.Second) + } +} + +func NewWorkManger(onFailed OnTaskFailed, onCompleted OnTaskCompleted) *SWorkManager { + return &SWorkManager{ + onFailed: onFailed, + onCompleted: onCompleted, + } +} diff --git a/pkg/cloudir/service/service.go b/pkg/cloudir/service/service.go index 52d3423c03..ff07cc1334 100644 --- a/pkg/cloudir/service/service.go +++ b/pkg/cloudir/service/service.go @@ -28,7 +28,7 @@ func StartService() { initHandlers(app) - cloudcommon.ServeForever(app, commonOpts, func() { + cloudcommon.ServeForeverWithCleanup(app, commonOpts, func() { etcd.CloseDefaultEtcdClient() }) } diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index d3ac984b57..b74769c2c6 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -1,22 +1,31 @@ package cloudprovider import ( + "context" "fmt" "errors" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/mcclient" ) var ( ErrNoSuchProvder = errors.New("no such provider") ) +type SCloudaccount struct { + Account string + Secret string +} + type ICloudProviderFactory interface { GetProvider(providerId, providerName, url, account, secret string) (ICloudProvider, error) GetId() string ValidateChangeBandwidth(instanceId string, bandwidth int64) error + ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) error + ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, cloudaccount string) (*SCloudaccount, error) } type ICloudProvider interface { @@ -40,6 +49,8 @@ type ICloudProvider interface { GetBalance() (float64, error) GetSubAccounts() ([]SSubAccount, error) + + SupportPrepaidResources() bool } var providerTable map[string]ICloudProviderFactory diff --git a/pkg/cloudprovider/fakeregion.go b/pkg/cloudprovider/fakeregion.go index 5ff1352a12..fc85e3481e 100644 --- a/pkg/cloudprovider/fakeregion.go +++ b/pkg/cloudprovider/fakeregion.go @@ -68,7 +68,7 @@ func (region *SFakeOnPremiseRegion) CreateIVpc(name string, desc string, cidr st return nil, ErrNotSupported } -func (region *SFakeOnPremiseRegion) CreateEIP(name string, bwMbps int, chargeType string) (ICloudEIP, error) { +func (region *SFakeOnPremiseRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (ICloudEIP, error) { return nil, ErrNotSupported } @@ -84,10 +84,34 @@ func (region *SFakeOnPremiseRegion) GetILoadBalancers() ([]ICloudLoadbalancer, e return nil, ErrNotSupported } -func (region *SFakeOnPremiseRegion) GetILoadbalancerAcls() ([]ICloudLoadbalancerAcl, error) { +func (region *SFakeOnPremiseRegion) GetILoadBalancerById(loadbalancerId string) (ICloudLoadbalancer, error) { return nil, ErrNotSupported } -func (region *SFakeOnPremiseRegion) GetILoadbalancerCertificates() ([]ICloudLoadbalancerCertificate, error) { +func (region *SFakeOnPremiseRegion) GetILoadBalancerAclById(aclId string) (ICloudLoadbalancerAcl, error) { + return nil, ErrNotSupported +} + +func (region *SFakeOnPremiseRegion) GetILoadBalancerCertificateById(certId string) (ICloudLoadbalancerCertificate, error) { + return nil, ErrNotSupported +} + +func (region *SFakeOnPremiseRegion) CreateILoadBalancerCertificate(cert *SLoadbalancerCertificate) (ICloudLoadbalancerCertificate, error) { + return nil, ErrNotImplemented +} + +func (region *SFakeOnPremiseRegion) GetILoadBalancerAcls() ([]ICloudLoadbalancerAcl, error) { + return nil, ErrNotSupported +} + +func (region *SFakeOnPremiseRegion) GetILoadBalancerCertificates() ([]ICloudLoadbalancerCertificate, error) { + return nil, ErrNotSupported +} + +func (region *SFakeOnPremiseRegion) CreateILoadBalancer(loadbalancer *SLoadbalancer) (ICloudLoadbalancer, error) { + return nil, ErrNotSupported +} + +func (region *SFakeOnPremiseRegion) CreateILoadBalancerAcl(acl *SLoadbalancerAccessControlList) (ICloudLoadbalancerAcl, error) { return nil, ErrNotSupported } diff --git a/pkg/cloudprovider/images.go b/pkg/cloudprovider/images.go new file mode 100644 index 0000000000..22b76f334c --- /dev/null +++ b/pkg/cloudprovider/images.go @@ -0,0 +1,59 @@ +package cloudprovider + +import ( + "time" +) + +const ( + IMAGE_STATUS_ACTIVE = "active" + IMAGE_STATUS_QUEUED = "queued" + IMAGE_STATUS_SAVING = "saving" + IMAGE_STATUS_KILLED = "killed" + IMAGE_STATUS_DELETED = "deleted" + + CachedImageTypeSystem = "system" + CachedImageTypeCustomized = "customized" + CachedImageTypeShared = "shared" + CachedImageTypeMarket = "market" +) + +type SImage struct { + Checksum string + // ContainerFormat string + CreatedAt time.Time + Deleted bool + DiskFormat string + Id string + IsPublic bool + MinDisk int + MinRam int + Name string + Owner string + Properties map[string]string + Protected bool + Size int64 + Status string + // UpdatedAt time.Time +} + +func CloudImage2Image(image ICloudImage) SImage { + return SImage{ + CreatedAt: image.GetCreateTime(), + Deleted: false, + DiskFormat: image.GetImageFormat(), + Id: image.GetId(), + IsPublic: image.GetImageType() != CachedImageTypeCustomized, + MinDisk: image.GetMinOsDiskSizeGb(), + MinRam: 0, + Name: image.GetName(), + Properties: map[string]string{ + "os_type": image.GetOsType(), + "os_distribution": image.GetOsDist(), + "os_version": image.GetOsVersion(), + "os_arch": image.GetOsArch(), + }, + Protected: true, + Size: image.GetSize(), + Status: image.GetImageStatus(), + } +} diff --git a/pkg/cloudprovider/instance.go b/pkg/cloudprovider/instance.go new file mode 100644 index 0000000000..331ba0224f --- /dev/null +++ b/pkg/cloudprovider/instance.go @@ -0,0 +1,84 @@ +package cloudprovider + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/util/ansible" + "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/cloudinit" + "yunion.io/x/onecloud/pkg/util/seclib2" +) + +type SDiskInfo struct { + StorageType string + SizeGB int +} + +type SManagedVMCreateConfig struct { + Name string + ExternalImageId string + OsDistribution string + OsVersion string + InstanceType string // InstanceType 不为空时,直接采用InstanceType创建机器。 + Cpu int + MemoryMB int + ExternalNetworkId string + IpAddr string + Description string + SysDisk SDiskInfo + DataDisks []SDiskInfo + PublicKey string + ExternalSecgroupId string + ExternalSecgroupIds []string + Password string + UserData string + + BillingCycle *billing.SBillingCycle +} + +func (vmConfig *SManagedVMCreateConfig) GetConfig(config *jsonutils.JSONDict) error { + if err := config.Unmarshal(vmConfig, "desc"); err != nil { + return err + } + if publicKey, _ := config.GetString("public_key"); len(publicKey) > 0 { + vmConfig.PublicKey = publicKey + } + + adminPublicKey, _ := config.GetString("admin_public_key") + projectPublicKey, _ := config.GetString("project_public_key") + oUserData, _ := config.GetString("user_data") + + vmConfig.UserData = generateUserData(adminPublicKey, projectPublicKey, oUserData) + + resetPassword := jsonutils.QueryBoolean(config, "reset_password", false) + vmConfig.Password, _ = config.GetString("password") + if resetPassword && len(vmConfig.Password) == 0 { + vmConfig.Password = seclib2.RandomPassword2(12) + } + return nil +} + +func generateUserData(adminPublicKey, projectPublicKey, oUserData string) string { + var oCloudConfig *cloudinit.SCloudConfig + + if len(oUserData) > 0 { + oCloudConfig, _ = cloudinit.ParseUserDataBase64(oUserData) + } + + ansibleUser := cloudinit.NewUser(ansible.PUBLIC_CLOUD_ANSIBLE_USER) + ansibleUser.SshKey(adminPublicKey).SshKey(projectPublicKey).SudoPolicy(cloudinit.USER_SUDO_NOPASSWD) + + cloudConfig := cloudinit.SCloudConfig{ + DisableRoot: 0, + SshPwauth: 1, + + Users: []cloudinit.SUser{ + ansibleUser, + }, + } + + if oCloudConfig != nil { + cloudConfig.Merge(oCloudConfig) + } + + return cloudConfig.UserDataBase64() +} diff --git a/pkg/cloudprovider/loadbalancer.go b/pkg/cloudprovider/loadbalancer.go new file mode 100644 index 0000000000..21fe546711 --- /dev/null +++ b/pkg/cloudprovider/loadbalancer.go @@ -0,0 +1,16 @@ +package cloudprovider + +import "yunion.io/x/onecloud/pkg/util/billing" + +type SLoadbalancer struct { + Name string + ZoneID string + VpcID string + NetworkID string + Address string + AddressType string + LoadbalancerSpec string + ChargeType string + Bandwidth int + billingCycle *billing.SBillingCycle +} diff --git a/pkg/cloudprovider/loadbalanceracl.go b/pkg/cloudprovider/loadbalanceracl.go new file mode 100644 index 0000000000..b8d708ebb0 --- /dev/null +++ b/pkg/cloudprovider/loadbalanceracl.go @@ -0,0 +1,11 @@ +package cloudprovider + +type SLoadbalancerAccessControlListEntry struct { + CIDR string + Comment string +} + +type SLoadbalancerAccessControlList struct { + Name string + Entrys []SLoadbalancerAccessControlListEntry +} diff --git a/pkg/cloudprovider/loadbalancerbackend.go b/pkg/cloudprovider/loadbalancerbackend.go new file mode 100644 index 0000000000..7f883d9cb4 --- /dev/null +++ b/pkg/cloudprovider/loadbalancerbackend.go @@ -0,0 +1,15 @@ +package cloudprovider + +type SLoadbalancerBackend struct { + Index int + Weight int + Port int + ID string + Name string + ExternalID string + BackendType string + BackendRole string + Address string + ZoneId string + HostName string +} diff --git a/pkg/cloudprovider/loadbalancerbackendgroup.go b/pkg/cloudprovider/loadbalancerbackendgroup.go new file mode 100644 index 0000000000..6f1a988585 --- /dev/null +++ b/pkg/cloudprovider/loadbalancerbackendgroup.go @@ -0,0 +1,7 @@ +package cloudprovider + +type SLoadbalancerBackendGroup struct { + Name string + GroupType string + Backends []SLoadbalancerBackend +} diff --git a/pkg/cloudprovider/loadbalancercertificate.go b/pkg/cloudprovider/loadbalancercertificate.go new file mode 100644 index 0000000000..6139365ac3 --- /dev/null +++ b/pkg/cloudprovider/loadbalancercertificate.go @@ -0,0 +1,7 @@ +package cloudprovider + +type SLoadbalancerCertificate struct { + Name string + PrivateKey string + Certificate string +} diff --git a/pkg/cloudprovider/loadbalancerlistener.go b/pkg/cloudprovider/loadbalancerlistener.go new file mode 100644 index 0000000000..85d95c13ad --- /dev/null +++ b/pkg/cloudprovider/loadbalancerlistener.go @@ -0,0 +1,48 @@ +package cloudprovider + +type SLoadbalancerListener struct { + Name string + LoadbalancerID string + ListenerType string + ListenerPort int + BackendGroupType string + BackendGroupID string + Scheduler string + AccessControlListStatus string + AccessControlListType string + AccessControlListID string + EnableHTTP2 bool + CertificateID string + Bandwidth int + Description string + EstablishedTimeout int + + HealthCheck string + HealthCheckTimeout int + HealthCheckDomain string + HealthCheckHttpCode string + HealthCheckURI string + HealthCheckInterval int + + HealthCheckRise int + HealthCheckFail int + + StickySession string + StickySessionCookie string + StickySessionType string + StickySessionCookieTimeout int + + BackendServerPort int + XForwardedFor bool + Gzip bool + + TLSCipherPolicy string +} + +type SLoadbalancerListenerRule struct { + Name string + Domain string + Path string + BackendGroupID string + BackendGroupType string +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 4f30728c77..8b4d9b6544 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -48,7 +48,7 @@ type ICloudRegion interface { SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error) CreateIVpc(name string, desc string, cidr string) (ICloudVpc, error) - CreateEIP(name string, bwMbps int, chargeType string) (ICloudEIP, error) + CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (ICloudEIP, error) GetISnapshots() ([]ICloudSnapshot, error) GetISnapshotById(snapshotId string) (ICloudSnapshot, error) @@ -59,9 +59,20 @@ type ICloudRegion interface { GetIStorages() ([]ICloudStorage, error) GetIStorageById(id string) (ICloudStorage, error) + GetIStoragecaches() ([]ICloudStoragecache, error) + GetIStoragecacheById(id string) (ICloudStoragecache, error) + GetILoadBalancers() ([]ICloudLoadbalancer, error) - GetILoadbalancerAcls() ([]ICloudLoadbalancerAcl, error) - GetILoadbalancerCertificates() ([]ICloudLoadbalancerCertificate, error) + GetILoadBalancerAcls() ([]ICloudLoadbalancerAcl, error) + GetILoadBalancerCertificates() ([]ICloudLoadbalancerCertificate, error) + + GetILoadBalancerById(loadbalancerId string) (ICloudLoadbalancer, error) + GetILoadBalancerAclById(aclId string) (ICloudLoadbalancerAcl, error) + GetILoadBalancerCertificateById(certId string) (ICloudLoadbalancerCertificate, error) + + CreateILoadBalancer(loadbalancer *SLoadbalancer) (ICloudLoadbalancer, error) + CreateILoadBalancerAcl(acl *SLoadbalancerAccessControlList) (ICloudLoadbalancerAcl, error) + CreateILoadBalancerCertificate(cert *SLoadbalancerCertificate) (ICloudLoadbalancerCertificate, error) GetProvider() string } @@ -83,6 +94,17 @@ type ICloudImage interface { Delete(ctx context.Context) error GetIStoragecache() ICloudStoragecache + + GetSize() int64 + GetImageType() string + GetImageStatus() string + GetOsType() string + GetOsDist() string + GetOsVersion() string + GetOsArch() string + GetMinOsDiskSizeGb() int + GetImageFormat() string + GetCreateTime() time.Time } type ICloudStoragecache interface { @@ -123,6 +145,8 @@ type ICloudStorage interface { GetIDiskById(idStr string) (ICloudDisk, error) GetMountPoint() string + + IsSysDiskStore() bool } type ICloudHost interface { @@ -156,12 +180,7 @@ type ICloudHost interface { GetManagerId() string - CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, vswitchId string, ipAddr string, desc string, - passwd string, storageType string, diskSizes []int, publicKey string, extSecGrpId string, userData string, billingCycle *billing.SBillingCycle) (ICloudVM, error) - // 使用instanceType创建实例。 - CreateVM2(name string, imgId string, sysDiskSize int, instanceType string, vswitchId string, ipAddr string, desc string, - passwd string, storageType string, diskSizes []int, publicKey string, extSecGrpId string, userData string, billingCycle *billing.SBillingCycle) (ICloudVM, error) - + CreateVM(desc *SManagedVMCreateConfig) (ICloudVM, error) GetIHostNics() ([]ICloudHostNetInterface, error) } @@ -384,9 +403,22 @@ type ICloudLoadbalancer interface { GetNetworkId() string GetVpcId() string GetZoneId() string + GetLoadbalancerSpec() string + GetChargeType() string - GetILoadbalancerListeners() ([]ICloudLoadbalancerListener, error) - GetILoadbalancerBackendGroups() ([]ICloudLoadbalancerBackendGroup, error) + Delete() error + + Start() error + Stop() error + + GetILoadBalancerListeners() ([]ICloudLoadbalancerListener, error) + GetILoadBalancerBackendGroups() ([]ICloudLoadbalancerBackendGroup, error) + + CreateILoadBalancerBackendGroup(group *SLoadbalancerBackendGroup) (ICloudLoadbalancerBackendGroup, error) + GetILoadBalancerBackendGroupById(groupId string) (ICloudLoadbalancerBackendGroup, error) + + CreateILoadBalancerListener(listener *SLoadbalancerListener) (ICloudLoadbalancerListener, error) + GetILoadBalancerListenerById(listenerId string) (ICloudLoadbalancerListener, error) } type ICloudLoadbalancerListener interface { @@ -410,11 +442,14 @@ type ICloudLoadbalancerListener interface { GetHealthCheckExp() string GetBackendGroupId() string + GetBackendServerPort() int // HTTP && HTTPS GetHealthCheckDomain() string GetHealthCheckURI() string GetHealthCheckCode() string + CreateILoadBalancerListenerRule(rule *SLoadbalancerListenerRule) (ICloudLoadbalancerListenerRule, error) + GetILoadBalancerListenerRuleById(ruleId string) (ICloudLoadbalancerListenerRule, error) GetILoadbalancerListenerRules() ([]ICloudLoadbalancerListenerRule, error) GetStickySession() string GetStickySessionType() string @@ -427,6 +462,12 @@ type ICloudLoadbalancerListener interface { GetCertificateId() string GetTLSCipherPolicy() string HTTP2Enabled() bool + + Start() error + Stop() error + Sync(listener *SLoadbalancerListener) error + + Delete() error } type ICloudLoadbalancerListenerRule interface { @@ -435,6 +476,8 @@ type ICloudLoadbalancerListenerRule interface { GetDomain() string GetPath() string GetBackendGroupId() string + + Delete() error } type ICloudLoadbalancerBackendGroup interface { @@ -443,6 +486,11 @@ type ICloudLoadbalancerBackendGroup interface { IsDefault() bool GetType() string GetILoadbalancerBackends() ([]ICloudLoadbalancerBackend, error) + AddBackendServer(serverId string, weight int, port int) (ICloudLoadbalancerBackend, error) + RemoveBackendServer(serverId string, weight int, port int) error + + Delete() error + Sync(name string) error } type ICloudLoadbalancerBackend interface { @@ -458,6 +506,9 @@ type ICloudLoadbalancerBackend interface { type ICloudLoadbalancerCertificate interface { ICloudResource + Sync(name, privateKey, publickKey string) error + Delete() error + GetCommonName() string GetSubjectAlternativeNames() string GetFingerprint() string // return value format: :,比如sha1:7454a14fdb8ae1ea8b2f72e458a24a76bd23ec19 @@ -467,16 +518,7 @@ type ICloudLoadbalancerCertificate interface { type ICloudLoadbalancerAcl interface { ICloudResource - // return array data like this: - // [ - // { - // "cidr":"10.10.12.0/24", - // "comment":"test data" - // }, - // { - // "cidr":"192.168.10.12", - // "comment":"test data2" - // } - // ] - GetAclEntries() *jsonutils.JSONArray + GetAclEntries() []SLoadbalancerAccessControlListEntry + Sync(acl *SLoadbalancerAccessControlList) error + Delete() error } diff --git a/pkg/cloudprovider/retry.go b/pkg/cloudprovider/retry.go index 7fb70fd6da..daee86d8bf 100644 --- a/pkg/cloudprovider/retry.go +++ b/pkg/cloudprovider/retry.go @@ -29,3 +29,19 @@ func RetryOnError(tryFunc func() error, errs []string, maxTries int) error { } return ErrTimeout } + +func RetryUntil(tryFunc func() (bool, error), maxTries int) error { + tried := 0 + for tried < maxTries { + stop, err := tryFunc() + if stop { + return nil + } + if err != nil { + return err + } + tried += 1 + time.Sleep(10 * time.Duration(tried) * time.Second) + } + return ErrTimeout +} diff --git a/pkg/cloudprovider/waitstatus.go b/pkg/cloudprovider/waitstatus.go index 10a8393cc3..cbe4d8841d 100644 --- a/pkg/cloudprovider/waitstatus.go +++ b/pkg/cloudprovider/waitstatus.go @@ -37,3 +37,15 @@ func WaitDeleted(res ICloudResource, interval time.Duration, timeout time.Durati } return ErrTimeout } + +func WaitCreated(interval time.Duration, timeout time.Duration, callback func() bool) error { + startTime := time.Now() + for time.Now().Sub(startTime) < timeout { + ok := callback() + if ok { + return nil + } + time.Sleep(interval) + } + return ErrTimeout +} diff --git a/pkg/compute/baremetal/diskconfig.go b/pkg/compute/baremetal/diskconfig.go index c0803bbb70..9eae1f6808 100644 --- a/pkg/compute/baremetal/diskconfig.go +++ b/pkg/compute/baremetal/diskconfig.go @@ -3,6 +3,7 @@ package baremetal import ( "encoding/json" "fmt" + "reflect" "regexp" "strconv" "strings" @@ -127,27 +128,36 @@ func ParseDiskConfig(desc string) (bdc BaremetalDiskConfig, err error) { } else if len(p) > 2 && p[0] == '(' && p[len(p)-1] == ')' { bdc.Splits = p[1 : len(p)-1] } else if utils.HasPrefix(p, "strip") { - bdc.Strip = parseStrip(p[len("strip"):], "k") + strip := parseStrip(p[len("strip"):], "k") + bdc.Strip = &strip } else if utils.HasPrefix(p, "adapter") { ada, _ := strconv.ParseInt(p[len("adapter"):], 0, 64) pada := int(ada) bdc.Adapter = &pada } else if p == "ra" { - bdc.RA = true + hasRA := true + bdc.RA = &hasRA } else if p == "nora" { - bdc.RA = false + noRA := false + bdc.RA = &noRA } else if p == "wt" { - bdc.WT = true + wt := true + bdc.WT = &wt } else if p == "wb" { - bdc.WT = false + wt := false + bdc.WT = &wt } else if p == "direct" { - bdc.Direct = true + direct := true + bdc.Direct = &direct } else if p == "cached" { - bdc.Direct = false + direct := false + bdc.Direct = &direct } else if p == "cachedbadbbu" { - bdc.Cachedbadbbu = true + cached := true + bdc.Cachedbadbbu = &cached } else if p == "nocachedbadbbu" { - bdc.Cachedbadbbu = false + cached := false + bdc.Cachedbadbbu = &cached } else { err = fmt.Errorf("ParseDiskConfig unkown option %q", p) return @@ -310,13 +320,13 @@ func MeetConfig( } } - if driver == DISK_DRIVER_MEGARAID && conf.Strip != 0 { + if driver == DISK_DRIVER_MEGARAID && conf.Strip != nil { minStripSize := storages[0].MinStripSize maxStripSize := storages[0].MaxStripSize - if maxStripSize != 0 && minStripSize != 0 { - size := conf.Strip + if maxStripSize != -1 && minStripSize != -1 { + size := *conf.Strip if size > maxStripSize || size < minStripSize { - return fmt.Errorf("%q input strip size out of range(%d, %d)", DISK_DRIVER_MEGARAID, minStripSize, maxStripSize) + return fmt.Errorf("%q input strip size out of range(%d, %d), input: %d", DISK_DRIVER_MEGARAID, minStripSize, maxStripSize, size) } } } @@ -407,6 +417,33 @@ func ExpandNoneConf(layouts []Layout) (ret []Layout) { return ret } +func GetLayoutRaidConfig(layouts []Layout) []*BaremetalDiskConfig { + var disk []*BaremetalStorage + ret := make([]*BaremetalDiskConfig, 0) + for _, layout := range layouts { + if layout.Conf.Conf == DISK_CONF_NONE && + sets.NewString(DISK_DRIVER_LINUX, DISK_DRIVER_PCIE).Has(layout.Disks[0].Driver) { + continue + } + if !reflect.DeepEqual(disk, layout.Disks) { + ret = append(ret, layout.Conf) + disk = layout.Disks + lastConf := ret[len(ret)-1] + /* + if 'size' in ret[-1]: + ret[-1]['size'] = [ret[-1]['size']] + */ + if lastConf.Size == nil { + lastConf.Size = make([]int64, 0) + } + } else { + lastConf := ret[len(ret)-1] + lastConf.Size = append(lastConf.Size, layout.Conf.Size...) + } + } + return ret +} + func CalculateLayout(confs []*BaremetalDiskConfig, storages []*BaremetalStorage) (layouts []Layout, err error) { var confIdx = 0 for len(storages) > 0 { @@ -415,7 +452,7 @@ func CalculateLayout(confs []*BaremetalDiskConfig, storages []*BaremetalStorage) conf = confs[confIdx] confIdx += 1 } else { - noneConf, _ := ParseDiskConfig("none") + noneConf, _ := ParseDiskConfig(DISK_CONF_NONE) conf = &noneConf } selected, storage1 := RetrieveStorages(conf, storages) @@ -599,3 +636,62 @@ func GetDiskSpecV2(storages []*BaremetalStorage) DiskDriverSpecs { } return spec } + +type DiskConfiguration struct { + Driver string + Adapter int + Block int64 + Size int64 +} + +func GetDiskConfigurations(layouts []Layout) []DiskConfiguration { + disks := make([]DiskConfiguration, 0) + for _, rr := range layouts { + driver := rr.Disks[0].Driver + adapter := rr.Disks[0].Adapter + block := rr.Disks[0].GetBlock() + raidConf := rr.Conf.Conf + if raidConf == DISK_CONF_NONE { + for _, d := range rr.Disks { + disks = append(disks, DiskConfiguration{Driver: driver, Adapter: adapter, Block: block, Size: d.Size}) + } + } else { + if len(rr.Conf.Size) != 0 { + for _, sz := range rr.Conf.Size { + disks = append(disks, DiskConfiguration{Driver: driver, Adapter: adapter, Block: block, Size: sz}) + } + } else { + disks = append(disks, DiskConfiguration{Driver: driver, Adapter: adapter, Block: block, Size: rr.Size}) + } + } + } + return disks +} + +type DriverAdapterDiskConfig struct { + Driver string + Adapter int + Configs []*BaremetalDiskConfig +} + +func GroupLayoutResultsByDriverAdapter(layouts []Layout) []*DriverAdapterDiskConfig { + ret := make([]*DriverAdapterDiskConfig, 0) + tbl := make(map[string]*DriverAdapterDiskConfig) + for _, layout := range layouts { + driver := layout.Disks[0].Driver + adapter := layout.Disks[0].Adapter + key := fmt.Sprintf("%s.%d", driver, adapter) + if item, ok := tbl[key]; ok { + item.Configs = append(item.Configs, layout.Conf) + } else { + item := &DriverAdapterDiskConfig{ + Driver: driver, + Adapter: adapter, + Configs: []*BaremetalDiskConfig{layout.Conf}, + } + ret = append(ret, item) + tbl[key] = item + } + } + return ret +} diff --git a/pkg/compute/baremetal/diskconfig_test.go b/pkg/compute/baremetal/diskconfig_test.go index 8cc7530084..388030f4f2 100644 --- a/pkg/compute/baremetal/diskconfig_test.go +++ b/pkg/compute/baremetal/diskconfig_test.go @@ -81,7 +81,7 @@ func TestParseDiskConfig(t *testing.T) { Count: 0, Splits: splits100_32, Adapter: &zAda, - Strip: tStrip64k, + Strip: &tStrip64k, Range: []int64{12, 13}, }, wantErr: false, @@ -342,24 +342,14 @@ func TestCalculateLayout(t *testing.T) { { "disks": [ { - "slot": 0, - "status": "", "rotate": true, - "adapter": 0, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 953344, "index": 12 }, { - "slot": 0, - "status": "", "rotate": true, - "adapter": 0, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 953344, "index": 13 } @@ -379,79 +369,51 @@ func TestCalculateLayout(t *testing.T) { 818176 ], "adapter": 0, - "cachedbadbbu": false, - "strip": 0, - "ra": false, - "wt": false, - "direct": false + "driver": "" }, "size": 953344 }, { "disks": [ { - "slot": 0, - "status": "", - "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, + "rotate": true, "index": 0 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 1 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 2 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 3 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 4 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 5 } @@ -464,79 +426,51 @@ func TestCalculateLayout(t *testing.T) { "splits": "", "size": null, "adapter": 2, - "cachedbadbbu": false, - "strip": 0, - "ra": false, - "wt": false, - "direct": false + "driver": "" }, "size": 14305280 }, { "disks": [ { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 6 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 7 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 8 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 9 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 10 }, { - "slot": 0, - "status": "", "rotate": true, "adapter": 2, "driver": "MegaRaid", - "model": "", - "enclousure": 0, "size": 2861056, "index": 11 } @@ -549,11 +483,7 @@ func TestCalculateLayout(t *testing.T) { "splits": "", "size": null, "adapter": 2, - "cachedbadbbu": false, - "strip": 0, - "ra": false, - "wt": false, - "direct": false + "driver": "" }, "size": 14305280 } @@ -1090,3 +1020,39 @@ func TestStorageLoad(t *testing.T) { } log.Debugf("layout: %s", layout) } + +func TestCalculateSize(t *testing.T) { + type args struct { + conf string + storages []*BaremetalStorage + } + tests := []struct { + name string + args args + want int64 + }{ + { + name: "NoneRaid", + args: args{ + conf: "", + storages: testStorages, + }, + want: 2861056*12 + 953344*2, + }, + { + name: "RAID1", + args: args{ + conf: DISK_CONF_RAID1, + storages: testStorages, + }, + want: 953344 * (12 + 2) / 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CalculateSize(tt.args.conf, tt.args.storages); got != tt.want { + t.Errorf("CalculateSize() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/compute/baremetal/types.go b/pkg/compute/baremetal/types.go index ec66dfa534..384178d9c6 100644 --- a/pkg/compute/baremetal/types.go +++ b/pkg/compute/baremetal/types.go @@ -72,17 +72,31 @@ var ( ) type BaremetalStorage struct { - Slot int `json:"slot"` - Status string `json:"status"` - Rotate bool `json:"rotate"` - Adapter int `json:"adapter"` - Driver string `json:"driver"` - Model string `json:"model"` - Enclosure int `json:"enclousure"` Size int64 `json:"size"` + Driver string `json:"driver"` + Rotate bool `json:"rotate"` + Dev string `json:"dev,omitempty"` + Sector int64 `json:"sector,omitempty"` + Block int64 `json:"block,omitempty"` + ModuleInfo string `json:"module,omitempty"` + Kernel string `json:"kernel,omitempty"` + PCIClass string `json:"pci_class,omitempty"` + Slot int `json:"slot,omitempty"` + Status string `json:"status,omitempty"` + Adapter int `json:"adapter,omitempty"` + Model string `json:"model,omitempty"` + Enclosure int `json:"enclousure,omitempty"` MinStripSize int64 `json:"min_strip_size,omitempty"` MaxStripSize int64 `json:"max_strip_size,omitempty"` Index int64 `json:"index"` + Addr string `json:"addr,omitempty"` +} + +func (s BaremetalStorage) GetBlock() int64 { + if s.Block <= 0 { + return 512 + } + return s.Block } type BaremetalDiskConfig struct { @@ -94,13 +108,13 @@ type BaremetalDiskConfig struct { Range []int64 `json:"range"` Splits string `json:"splits"` Size []int64 `json:"size"` - Adapter *int `json:"adapter"` + Adapter *int `json:"adapter,omitempty"` Driver string `json:"driver"` - Cachedbadbbu bool `json:"cachedbadbbu"` - Strip int64 `json:"strip"` - RA bool `json:"ra"` - WT bool `json:"wt"` - Direct bool `json:"direct"` + Cachedbadbbu *bool `json:"cachedbadbbu,omitempty"` + Strip *int64 `json:"strip,omitempty"` + RA *bool `json:"ra,omitempty"` + WT *bool `json:"wt,omitempty"` + Direct *bool `json:"direct,omitempty"` } type Disk struct { diff --git a/pkg/compute/guestdrivers/aliyun.go b/pkg/compute/guestdrivers/aliyun.go index e624b61efb..8372a907ef 100644 --- a/pkg/compute/guestdrivers/aliyun.go +++ b/pkg/compute/guestdrivers/aliyun.go @@ -15,7 +15,6 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/billing" - "yunion.io/x/onecloud/pkg/util/seclib2" ) type SAliyunGuestDriver struct { @@ -31,6 +30,14 @@ func (self *SAliyunGuestDriver) GetHypervisor() string { return models.HYPERVISOR_ALIYUN } +func (self *SAliyunGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_CLOUD_EFFICIENCY +} + +func (self *SAliyunGuestDriver) GetMinimalSysDiskSizeGb() int { + return 20 +} + func (self *SAliyunGuestDriver) ChooseHostStorage(host *models.SHost, backend string) *models.SStorage { storages := host.GetAttachedStorages("") for i := 0; i < len(storages); i += 1 { @@ -38,7 +45,13 @@ func (self *SAliyunGuestDriver) ChooseHostStorage(host *models.SHost, backend st return &storages[i] } } - for _, stype := range []string{"cloud_efficiency", "cloud_ssd", "cloud", "ephemeral_ssd"} { + for _, stype := range []string{ + models.STORAGE_CLOUD_EFFICIENCY, + models.STORAGE_CLOUD_SSD, + models.STORAGE_CLOUD_ESSD, + models.STORAGE_PUBLIC_CLOUD, + models.STORAGE_EPHEMERAL_SSD, + } { for i := 0; i < len(storages); i += 1 { if storages[i].StorageType == stype { return &storages[i] @@ -93,84 +106,59 @@ func (self *SAliyunGuestDriver) ValidateCreateData(ctx context.Context, userCred if data.Contains("net.0") && data.Contains("net.1") { return nil, httperrors.NewInputParameterError("cannot support more than 1 nic") } + for i := 0; data.Contains(fmt.Sprintf("disk.%d", i)); i++ { + disk := models.SDiskConfig{} + if err := data.Unmarshal(&disk, fmt.Sprintf("disk.%d", i)); err != nil { + return nil, httperrors.NewInputParameterError("invalid diskinfo of index %d", i) + } + if i == 0 && (disk.SizeMb < 20*1024 || disk.SizeMb > 500*1024) { + return nil, httperrors.NewInputParameterError("The system disk size must be in the range of 20GB ~ 500Gb") + } + switch disk.Backend { + case models.STORAGE_CLOUD_EFFICIENCY, models.STORAGE_CLOUD_SSD, models.STORAGE_CLOUD_ESSD: + if disk.SizeMb < 20*1024 || disk.SizeMb > 32768*1024 { + return nil, httperrors.NewInputParameterError("The %s disk size must be in the range of 20GB ~ 32768GB", disk.Backend) + } + case models.STORAGE_PUBLIC_CLOUD: + if disk.SizeMb < 5*1024 || disk.SizeMb > 2000*1024 { + return nil, httperrors.NewInputParameterError("The %s disk size must be in the range of 5GB ~ 2000GB", disk.Backend) + } + case models.STORAGE_EPHEMERAL_SSD: + if disk.SizeMb < 5*1024 || disk.SizeMb > 800*1024 { + return nil, httperrors.NewInputParameterError("The %s disk size must be in the range of 5GB ~ 800GB", disk.Backend) + } + } + } return data, nil } func (self *SAliyunGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } log.Debugf("RequestDeployGuestOnHost: %s", config) + desc := cloudprovider.SManagedVMCreateConfig{} + if err := desc.GetConfig(config); err != nil { + return err + } + action, err := config.GetString("action") if err != nil { return err } - publicKey, _ := config.GetString("public_key") - - adminPublicKey, _ := config.GetString("admin_public_key") - projectPublicKey, _ := config.GetString("project_public_key") - oUserData, _ := config.GetString("user_data") - - userData := generateUserData(adminPublicKey, projectPublicKey, oUserData) - - resetPassword := jsonutils.QueryBoolean(config, "reset_password", false) - passwd, _ := config.GetString("password") - if resetPassword && len(passwd) == 0 { - passwd = seclib2.RandomPassword2(12) - } - ihost, err := host.GetIHost() if err != nil { return err } - desc := SManagedVMCreateConfig{} - err = config.Unmarshal(&desc, "desc") - if err != nil { - return err - } - if action == "create" { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - nets := guest.GetNetworks() - net := nets[0].GetNetwork() - vpc := net.GetVpc() - iregion, err := host.GetIRegion() - if err != nil { - return nil, err - } - - secgroupCache := models.SecurityGroupCacheManager.Register(ctx, task.GetUserCred(), desc.SecGroupId, vpc.Id, vpc.CloudregionId, vpc.ManagerId) - if secgroupCache == nil { - return nil, fmt.Errorf("failed to registor secgroupCache for secgroup: %s, vpc: %s", desc.SecGroupId, vpc.Name) - } - - secgroupExtId, err := iregion.SyncSecurityGroup(secgroupCache.ExternalId, vpc.ExternalId, desc.SecGroupName, "", desc.SecRules) - if err != nil { - log.Errorf("SyncSecurityGroup fail %s", err) - return nil, err - } - if err := secgroupCache.SetExternalId(secgroupExtId); err != nil { - return nil, fmt.Errorf("failed to set externalId for secgroup %s externalId %s: error: %v", desc.SecGroupId, secgroupExtId, err) - } - - var createErr error - var iVM cloudprovider.ICloudVM - - var bc *billing.SBillingCycle - if desc.BillingCycle.IsValid() { - bc = &desc.BillingCycle - } - - if len(desc.InstanceType) > 0 { - iVM, createErr = ihost.CreateVM2(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.InstanceType, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, bc) - } else { - iVM, createErr = ihost.CreateVM(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.Cpu, desc.Memory, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, bc) - } - + iVM, createErr := ihost.CreateVM(&desc) if createErr != nil { return nil, createErr } @@ -187,7 +175,7 @@ func (self *SAliyunGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) @@ -201,37 +189,23 @@ func (self *SAliyunGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu params := task.GetParams() log.Debugf("Deploy VM params %s", params.String()) - name, _ := params.GetString("name") - description, _ := params.GetString("description") - publicKey, _ := config.GetString("public_key") - // resetPassword := jsonutils.QueryBoolean(params, "reset_password", false) deleteKeypair := jsonutils.QueryBoolean(params, "__delete_keypair__", false) - //password, _ := params.GetString("password") - //if resetPassword && len(password) == 0 { - // password = seclib2.RandomPassword2(12) - //} - - /* - publicKey := "" - if k, e := config.GetString("public_key"); e == nil { - publicKey = k - }*/ taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - if len(userData) > 0 { - err := iVM.UpdateUserData(userData) + if len(desc.UserData) > 0 { + err := iVM.UpdateUserData(desc.UserData) if err != nil { log.Errorf("update userdata fail %s", err) } } - err := iVM.DeployVM(ctx, name, passwd, publicKey, deleteKeypair, description) + err := iVM.DeployVM(ctx, desc.Name, desc.Password, desc.PublicKey, deleteKeypair, desc.Description) if err != nil { return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) @@ -244,14 +218,14 @@ func (self *SAliyunGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu } taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - if len(userData) > 0 { - err := iVM.UpdateUserData(userData) + if len(desc.UserData) > 0 { + err := iVM.UpdateUserData(desc.UserData) if err != nil { log.Errorf("update userdata fail %s", err) } } - diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, passwd, publicKey, desc.SysDiskSize) + diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, desc.Password, desc.PublicKey, desc.SysDisk.SizeGB) if err != nil { return nil, err } @@ -292,7 +266,7 @@ func (self *SAliyunGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu } } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) diff --git a/pkg/compute/guestdrivers/aws.go b/pkg/compute/guestdrivers/aws.go index ad8bf44b90..9854e065be 100644 --- a/pkg/compute/guestdrivers/aws.go +++ b/pkg/compute/guestdrivers/aws.go @@ -29,6 +29,14 @@ func (self *SAwsGuestDriver) GetHypervisor() string { return models.HYPERVISOR_AWS } +func (self *SAwsGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_GP2_SSD +} + +func (self *SAwsGuestDriver) GetMinimalSysDiskSizeGb() int { + return 10 +} + func (self *SAwsGuestDriver) ChooseHostStorage(host *models.SHost, backend string) *models.SStorage { storages := host.GetAttachedStorages("") for i := 0; i < len(storages); i += 1 { @@ -37,7 +45,13 @@ func (self *SAwsGuestDriver) ChooseHostStorage(host *models.SHost, backend strin } } - for _, stype := range []string{"gp2", "io1", "st1", "sc1", "standard"} { + for _, stype := range []string{ + models.STORAGE_GP2_SSD, + models.STORAGE_IO1_SSD, + models.STORAGE_ST1_HDD, + models.STORAGE_SC1_HDD, + models.STORAGE_STANDARD_HDD, + } { for i := 0; i < len(storages); i += 1 { if storages[i].StorageType == stype { return &storages[i] @@ -90,9 +104,16 @@ func (self *SAwsGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *mode } func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } log.Debugf("RequestDeployGuestOnHost: %s", config) - + desc := cloudprovider.SManagedVMCreateConfig{} + if err := desc.GetConfig(config); err != nil { + return err + } action, err := config.GetString("action") if err != nil { return err @@ -103,56 +124,11 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest return err } - desc := SManagedVMCreateConfig{} - err = config.Unmarshal(&desc, "desc") - if err != nil { - return err - } - publicKey, _ := config.GetString("public_key") - passwd, _ := config.GetString("password") - - adminPublicKey, _ := config.GetString("admin_public_key") - projectPublicKey, _ := config.GetString("project_public_key") - oUserData, _ := config.GetString("user_data") - - userData := generateUserData(adminPublicKey, projectPublicKey, oUserData) - switch action { case "create": taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - nets := guest.GetNetworks() - net := nets[0].GetNetwork() - vpc := net.GetVpc() - - iregion, err := host.GetIRegion() - if err != nil { - return nil, err - } - - secgroupCache := models.SecurityGroupCacheManager.Register(ctx, task.GetUserCred(), desc.SecGroupId, vpc.Id, vpc.CloudregionId, vpc.ManagerId) - if secgroupCache == nil { - return nil, fmt.Errorf("failed to registor secgroupCache for secgroup: %s, vpc: %s", desc.SecGroupId, vpc.Name) - } - - secgroupExtId, err := iregion.SyncSecurityGroup(secgroupCache.ExternalId, vpc.ExternalId, desc.SecGroupName, "", desc.SecRules) - if err != nil { - log.Errorf("SyncSecurityGroup fail %s", err) - return nil, err - } - if err := secgroupCache.SetExternalId(secgroupExtId); err != nil { - return nil, fmt.Errorf("failed to set externalId for secgroup %s externalId %s: error: %v", desc.SecGroupId, secgroupExtId, err) - } - - var createErr error - var iVM cloudprovider.ICloudVM - if len(desc.InstanceType) > 0 { - iVM, createErr = ihost.CreateVM2(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.InstanceType, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, nil) - } else { - iVM, createErr = ihost.CreateVM(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.Cpu, desc.Memory, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, nil) - } + iVM, createErr := ihost.CreateVM(&desc) if createErr != nil { return nil, createErr } @@ -170,7 +146,7 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) case "deploy": @@ -182,19 +158,15 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest params := task.GetParams() log.Debugf("Deploy VM params %s", params.String()) - - name, _ := params.GetString("name") - description, _ := params.GetString("description") - publicKey, _ := config.GetString("public_key") deleteKeypair := jsonutils.QueryBoolean(params, "__delete_keypair__", false) taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - err := iVM.DeployVM(ctx, name, passwd, publicKey, deleteKeypair, description) + err := iVM.DeployVM(ctx, desc.Name, desc.Password, desc.PublicKey, deleteKeypair, desc.Description) if err != nil { return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, desc.Password, action) return data, nil }) case "rebuild": @@ -205,7 +177,7 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest } taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, passwd, publicKey, desc.SysDiskSize) + diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, desc.Password, desc.PublicKey, desc.SysDisk.SizeGB) if err != nil { return nil, err } @@ -246,7 +218,7 @@ func (self *SAwsGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest } } - data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, desc.Password, action) return data, nil }) diff --git a/pkg/compute/guestdrivers/azure.go b/pkg/compute/guestdrivers/azure.go index 6004d216d5..72d1e4cec4 100644 --- a/pkg/compute/guestdrivers/azure.go +++ b/pkg/compute/guestdrivers/azure.go @@ -35,6 +35,14 @@ func (self *SAzureGuestDriver) GetHypervisor() string { return models.HYPERVISOR_AZURE } +func (self *SAzureGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_STANDARD_LRS +} + +func (self *SAzureGuestDriver) GetMinimalSysDiskSizeGb() int { + return 10 +} + func (self *SAzureGuestDriver) ChooseHostStorage(host *models.SHost, backend string) *models.SStorage { storages := host.GetAttachedStorages("") for i := 0; i < len(storages); i += 1 { @@ -42,7 +50,11 @@ func (self *SAzureGuestDriver) ChooseHostStorage(host *models.SHost, backend str return &storages[i] } } - for _, stype := range []string{"standard_lrs", "standardssd_lrs", "premium_lrs"} { + for _, stype := range []string{ + models.STORAGE_STANDARD_LRS, + models.STORAGE_STANDARDSSD_LRS, + models.STORAGE_PREMIUM_LRS, + } { for i := 0; i < len(storages); i += 1 { if storages[i].StorageType == stype { return &storages[i] @@ -116,24 +128,16 @@ func (self *SAzureGuestDriver) ValidateUpdateData(ctx context.Context, userCred } func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) - publicKey, _ := config.GetString("public_key") - resetPassword := jsonutils.QueryBoolean(config, "reset_password", false) - passwd, _ := config.GetString("password") - if resetPassword && len(passwd) == 0 { - passwd = seclib2.RandomPassword2(12) - } - - adminPublicKey, _ := config.GetString("admin_public_key") - projectPublicKey, _ := config.GetString("project_public_key") - oUserData, _ := config.GetString("user_data") - - userData := generateUserData(adminPublicKey, projectPublicKey, oUserData) - - desc := SManagedVMCreateConfig{} - if err := config.Unmarshal(&desc, "desc"); err != nil { + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) return err } + desc := cloudprovider.SManagedVMCreateConfig{} + if err := desc.GetConfig(config); err != nil { + return err + } + action, err := config.GetString("action") if err != nil { return err @@ -144,48 +148,12 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue } if action == "create" { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - if len(passwd) == 0 { + if len(desc.Password) == 0 { //Azure创建必须要设置密码 - passwd = seclib2.RandomPassword2(12) + desc.Password = seclib2.RandomPassword2(12) } - nets := guest.GetNetworks() - net := nets[0].GetNetwork() - vpc := net.GetVpc() - - iregion, err := host.GetIRegion() - if err != nil { - return nil, err - } - - vpcId := "normal" - if strings.HasSuffix(host.Name, "-classic") { - vpcId = "classic" - } - - secgroupCache := models.SecurityGroupCacheManager.Register(ctx, task.GetUserCred(), desc.SecGroupId, vpcId, vpc.CloudregionId, vpc.ManagerId) - if secgroupCache == nil { - return nil, fmt.Errorf("failed to registor secgroupCache for secgroup: %s, vpc: %s", desc.SecGroupId, vpc.Name) - } - - secgroupExtId, err := iregion.SyncSecurityGroup(secgroupCache.ExternalId, vpcId, desc.SecGroupName, "", desc.SecRules) - if err != nil { - log.Errorf("SyncSecurityGroup fail %s", err) - return nil, err - } - if err := secgroupCache.SetExternalId(secgroupExtId); err != nil { - return nil, fmt.Errorf("failed to set externalId for secgroup %s externalId %s: error: %v", desc.SecGroupId, secgroupExtId, err) - } - - var createErr error - var iVM cloudprovider.ICloudVM - if len(desc.InstanceType) > 0 { - iVM, createErr = ihost.CreateVM2(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.InstanceType, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, nil) - } else { - iVM, createErr = ihost.CreateVM(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.Cpu, desc.Memory, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, nil) - } + iVM, createErr := ihost.CreateVM(&desc) if createErr != nil { return nil, createErr @@ -200,7 +168,7 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, desc.Password, action) return data, nil }) } else if action == "deploy" { @@ -211,18 +179,14 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue } params := task.GetParams() log.Debugf("Deploy VM params %s", params.String()) - - name, _ := params.GetString("name") - description, _ := params.GetString("description") - publicKey, _ := config.GetString("public_key") deleteKeypair := jsonutils.QueryBoolean(params, "__delete_keypair__", false) taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - err := iVM.DeployVM(ctx, name, passwd, publicKey, deleteKeypair, description) + err := iVM.DeployVM(ctx, desc.Name, desc.Password, desc.PublicKey, deleteKeypair, desc.Description) if err != nil { return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, desc.Password, action) return data, nil }) } else if action == "rebuild" { @@ -233,13 +197,13 @@ func (self *SAzureGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gue } taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - _, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, passwd, publicKey, desc.SysDiskSize) + _, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, desc.Password, desc.PublicKey, desc.SysDisk.SizeGB) if err != nil { return nil, err } log.Debugf("VMrebuildRoot %s, and status is ready", iVM.GetGlobalId()) - data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, ansible.PUBLIC_CLOUD_ANSIBLE_USER, desc.Password, action) return data, nil }) @@ -324,3 +288,7 @@ func (self *SAzureGuestDriver) RequestSyncConfigOnHost(ctx context.Context, gues func (self *SAzureGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { return false } + +func (self *SAzureGuestDriver) NeedStopForChangeSpec() bool { + return false +} diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go index ef8dabb121..b7bd959c6f 100644 --- a/pkg/compute/guestdrivers/baremetals.go +++ b/pkg/compute/guestdrivers/baremetals.go @@ -34,6 +34,14 @@ func (self *SBaremetalGuestDriver) GetHypervisor() string { return models.HYPERVISOR_BAREMETAL } +func (self *SBaremetalGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_LOCAL +} + +func (self *SBaremetalGuestDriver) GetMinimalSysDiskSizeGb() int { + return options.Options.DefaultDiskSizeMB / 1024 +} + func (self *SBaremetalGuestDriver) GetMaxSecurityGroupCount() int { //暂不支持绑定安全组 return 0 @@ -97,7 +105,7 @@ func (self *SBaremetalGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk } func (self *SBaremetalGuestDriver) GetNamedNetworkConfiguration(guest *models.SGuest, userCred mcclient.TokenCredential, host *models.SHost, netConfig *models.SNetworkConfig) (*models.SNetwork, string, int8, models.IPAddlocationDirection) { - netif, net := host.GetNetinterfaceWithNetworkAndCredential(netConfig.Network, userCred, netConfig.Reserved) + netif, net := host.GetNetinterfaceWithIdAndCredential(netConfig.Network, userCred, netConfig.Reserved) if netif != nil { return net, netif.Mac, netif.Index, models.IPAllocationStepup } @@ -105,7 +113,7 @@ func (self *SBaremetalGuestDriver) GetNamedNetworkConfiguration(guest *models.SG } func (self *SBaremetalGuestDriver) GetRandomNetworkTypes() []string { - return []string{models.SERVER_TYPE_BAREMETAL} + return []string{models.NETWORK_TYPE_BAREMETAL, models.NETWORK_TYPE_GUEST} } func (self *SBaremetalGuestDriver) Attach2RandomNetwork(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, netConfig *models.SNetworkConfig, pendingUsage quotas.IQuota) error { @@ -113,6 +121,10 @@ func (self *SBaremetalGuestDriver) Attach2RandomNetwork(guest *models.SGuest, ct netsAvaiable := make([]models.SNetwork, 0) netifIndexs := make(map[string]*models.SNetInterface, 0) + netTypes := guest.GetDriver().GetRandomNetworkTypes() + if len(netConfig.NetType) > 0 { + netTypes = []string{netConfig.NetType} + } var wirePattern *regexp.Regexp if len(netConfig.Wire) > 0 { wirePattern = regexp.MustCompile(netConfig.Wire) @@ -130,9 +142,9 @@ func (self *SBaremetalGuestDriver) Attach2RandomNetwork(guest *models.SGuest, ct } var net *models.SNetwork if netConfig.Private { - net, _ = wire.GetCandidatePrivateNetwork(userCred, netConfig.Exit, models.SERVER_TYPE_BAREMETAL) + net, _ = wire.GetCandidatePrivateNetwork(userCred, netConfig.Exit, netTypes) } else { - net, _ = wire.GetCandidatePublicNetwork(netConfig.Exit, models.SERVER_TYPE_BAREMETAL) + net, _ = wire.GetCandidatePublicNetwork(netConfig.Exit, netTypes) } if net != nil { netsAvaiable = append(netsAvaiable, *net) @@ -142,10 +154,10 @@ func (self *SBaremetalGuestDriver) Attach2RandomNetwork(guest *models.SGuest, ct if len(netsAvaiable) == 0 { return fmt.Errorf("No appropriate host virtual network...") } - net := models.ChooseCandidateNetworks(netsAvaiable, netConfig.Exit, models.SERVER_TYPE_BAREMETAL) + net := models.ChooseCandidateNetworks(netsAvaiable, netConfig.Exit, netTypes) if net != nil { netif := netifIndexs[net.Id] - return guest.Attach2Network(ctx, userCred, net, pendingUsage, "", netif.Mac, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, netif.Index, false, models.IPAllocationStepup, false) + return guest.Attach2Network(ctx, userCred, net, pendingUsage, "", netif.Mac, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, netif.Index, false, models.IPAllocationStepup, false, "") } return fmt.Errorf("No appropriate host virtual network...") } @@ -283,13 +295,12 @@ func (self *SBaremetalGuestDriver) ValidateCreateHostData(ctx context.Context, u if host.GetBaremetalServer() != nil { return nil, httperrors.NewInsufficientResourceError("Baremetal %s is occupied", bmName) } - data.Set("prefer_baremetal_id", jsonutils.NewString(host.Id)) data.Set("vmem_size", jsonutils.NewInt(int64(host.MemSize))) data.Set("vcpu_count", jsonutils.NewInt(int64(host.CpuCount))) return data, nil } -func (self *SBaremetalGuestDriver) GetJsonDescAtHost(ctx context.Context, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { +func (self *SBaremetalGuestDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { return guest.GetJsonDescAtBaremetal(ctx, host) } @@ -352,7 +363,11 @@ func (self *SBaremetalGuestDriver) OnGuestDeployTaskDataReceived(ctx context.Con } func (self *SBaremetalGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } val, _ := config.GetString("action") if len(val) == 0 { val = "deploy" @@ -362,7 +377,7 @@ func (self *SBaremetalGuestDriver) RequestDeployGuestOnHost(ctx context.Context, } url := fmt.Sprintf("/baremetals/%s/servers/%s/%s", host.Id, guest.Id, val) headers := task.GetTaskRequestHeader() - _, err := host.BaremetalSyncRequest(ctx, "POST", url, headers, config) + _, err = host.BaremetalSyncRequest(ctx, "POST", url, headers, config) return err } @@ -380,7 +395,7 @@ func (self *SBaremetalGuestDriver) StartGuestDetachdiskTask(ctx context.Context, } func (self *SBaremetalGuestDriver) StartGuestAttachDiskTask(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, params *jsonutils.JSONDict, parentTaskId string) error { - return fmt.Errorf("Cannot attach disk from a baremetal serer") + return fmt.Errorf("Cannot attach disk to a baremetal serer") } func (self *SBaremetalGuestDriver) StartSuspendTask(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, params *jsonutils.JSONDict, parentTaskId string) error { diff --git a/pkg/compute/guestdrivers/base.go b/pkg/compute/guestdrivers/base.go index 7fc6802013..5321e6c8c2 100644 --- a/pkg/compute/guestdrivers/base.go +++ b/pkg/compute/guestdrivers/base.go @@ -158,7 +158,14 @@ func (self *SBaseGuestDriver) StartGuestResetTask(guest *models.SGuest, ctx cont } func (self *SBaseGuestDriver) StartGuestRestartTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { - return fmt.Errorf("Not Implement") + data := jsonutils.NewDict() + data.Set("is_force", jsonutils.NewBool(isForce)) + task, err := taskman.TaskManager.NewTask(ctx, "GuestRestartTask", guest, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (self *SBaseGuestDriver) RequestSoftReset(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { @@ -173,7 +180,7 @@ func (self *SBaseGuestDriver) DoGuestCreateDisksTask(ctx context.Context, guest return fmt.Errorf("Not Implement") } -func (self *SBaseGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, vcpuCount, vmemSize int64) error { +func (self *SBaseGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, instanceType string, vcpuCount, vmemSize int64) error { return fmt.Errorf("Not Implement") } @@ -220,3 +227,11 @@ func (self *SBaseGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) func (self *SBaseGuestDriver) RequestRenewInstance(guest *models.SGuest, bc billing.SBillingCycle) (time.Time, error) { return time.Time{}, nil } + +func (self *SBaseGuestDriver) IsSupportEip() bool { + return false +} + +func (self *SBaseGuestDriver) NeedStopForChangeSpec() bool { + return true +} diff --git a/pkg/compute/guestdrivers/container.go b/pkg/compute/guestdrivers/container.go index 635a6b30e0..0be43be85b 100644 --- a/pkg/compute/guestdrivers/container.go +++ b/pkg/compute/guestdrivers/container.go @@ -5,9 +5,11 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/httputils" @@ -48,6 +50,14 @@ func (self *SContainerDriver) GetHypervisor() string { return models.HYPERVISOR_CONTAINER } +func (self *SContainerDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_LOCAL +} + +func (self *SContainerDriver) GetMinimalSysDiskSizeGb() int { + return options.Options.DefaultDiskSizeMB / 1024 +} + func (self *SContainerDriver) RequestGuestCreateAllDisks(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { // do nothing, call next stage task.ScheduleRun(nil) @@ -126,12 +136,16 @@ func (self *SContainerDriver) OnGuestDeployTaskComplete(ctx context.Context, gue return nil } -func (self *SContainerDriver) GetJsonDescAtHost(ctx context.Context, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { +func (self *SContainerDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { return guest.GetJsonDescAtHypervisor(ctx, host) } func (self *SContainerDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } config.Add(jsonutils.JSONTrue, "k8s_pod") action, err := config.GetString("action") if err != nil { @@ -158,7 +172,7 @@ func (self *SContainerDriver) DoGuestCreateDisksTask(ctx context.Context, guest return self.newUnsupportOperationError("create disk") } -func (self *SContainerDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, vcpuCount, vmemSize int64) error { +func (self *SContainerDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, instanceType string, vcpuCount, vmemSize int64) error { return self.newUnsupportOperationError("change config") } @@ -168,5 +182,9 @@ func (self *SContainerDriver) RequestRebuildRootDisk(ctx context.Context, guest } func (self *SContainerDriver) GetRandomNetworkTypes() []string { - return []string{models.SERVER_TYPE_CONTAINER, models.SERVER_TYPE_GUEST} + return []string{models.NETWORK_TYPE_CONTAINER, models.NETWORK_TYPE_GUEST} +} + +func (self *SContainerDriver) StartGuestRestartTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { + return fmt.Errorf("Not Implement") } diff --git a/pkg/compute/guestdrivers/esxi.go b/pkg/compute/guestdrivers/esxi.go index fbc5fc9778..ebc2abbb6b 100644 --- a/pkg/compute/guestdrivers/esxi.go +++ b/pkg/compute/guestdrivers/esxi.go @@ -11,6 +11,8 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/onecloud/pkg/util/httputils" ) @@ -28,6 +30,14 @@ func (self *SESXiGuestDriver) GetHypervisor() string { return models.HYPERVISOR_ESXI } +func (self *SESXiGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_LOCAL +} + +func (self *SESXiGuestDriver) GetMinimalSysDiskSizeGb() int { + return options.Options.DefaultDiskSizeMB / 1024 +} + func (self *SESXiGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { task.ScheduleRun(nil) return nil @@ -93,12 +103,16 @@ func (self *SESXiGuestDriver) RequestDetachDisk(ctx context.Context, guest *mode return guest.StartSyncTask(ctx, task.GetUserCred(), false, task.GetTaskId()) } -func (self *SESXiGuestDriver) GetJsonDescAtHost(ctx context.Context, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { +func (self *SESXiGuestDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { return guest.GetJsonDescAtHypervisor(ctx, host) } func (self *SESXiGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } log.Debugf("RequestDeployGuestOnHost: %s", config) if !host.IsEsxiAgentReady() { @@ -169,3 +183,7 @@ func (self *SESXiGuestDriver) DoGuestCreateDisksTask(ctx context.Context, guest func (self *SESXiGuestDriver) RequestRenewInstance(guest *models.SGuest, bc billing.SBillingCycle) (time.Time, error) { return time.Time{}, nil } + +func (self *SESXiGuestDriver) IsSupportEip() bool { + return false +} diff --git a/pkg/compute/guestdrivers/huawei.go b/pkg/compute/guestdrivers/huawei.go index 3ae730af55..e516fa9a1a 100644 --- a/pkg/compute/guestdrivers/huawei.go +++ b/pkg/compute/guestdrivers/huawei.go @@ -3,9 +3,12 @@ package guestdrivers import ( "context" "fmt" + "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/billing" @@ -25,6 +28,14 @@ func (self *SHuaweiGuestDriver) GetHypervisor() string { return models.HYPERVISOR_HUAWEI } +func (self *SHuaweiGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_HUAWEI_SATA +} + +func (self *SHuaweiGuestDriver) GetMinimalSysDiskSizeGb() int { + return 10 +} + func (self *SHuaweiGuestDriver) ChooseHostStorage(host *models.SHost, backend string) *models.SStorage { storages := host.GetAttachedStorages("") for i := 0; i < len(storages); i += 1 { @@ -83,11 +94,156 @@ func (self *SHuaweiGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *m } func (self *SHuaweiGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - // todo: implement me + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } + log.Debugf("RequestDeployGuestOnHost: %s", config) + + desc := cloudprovider.SManagedVMCreateConfig{} + if err := desc.GetConfig(config); err != nil { + return err + } + + action, err := config.GetString("action") + if err != nil { + return err + } + + ihost, err := host.GetIHost() + if err != nil { + return err + } + + if action == "create" { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + + iVM, createErr := ihost.CreateVM(&desc) + if createErr != nil { + return nil, createErr + } + log.Debugf("VMcreated %s, wait status ready ...", iVM.GetGlobalId()) + err = cloudprovider.WaitStatus(iVM, models.VM_RUNNING, time.Second*5, time.Second*1800) + if err != nil { + return nil, err + } + log.Debugf("VMcreated %s, and status is ready", iVM.GetGlobalId()) + + iVM, err = ihost.GetIVMById(iVM.GetGlobalId()) + if err != nil { + log.Errorf("cannot find vm %s", err) + return nil, err + } + + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) + + return data, nil + }) + } else if action == "deploy" { + iVM, err := ihost.GetIVMById(guest.GetExternalId()) + if err != nil || iVM == nil { + log.Errorf("cannot find vm %s", err) + return fmt.Errorf("cannot find vm") + } + + params := task.GetParams() + log.Debugf("Deploy VM params %s", params.String()) + + deleteKeypair := jsonutils.QueryBoolean(params, "__delete_keypair__", false) + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + + if len(desc.UserData) > 0 { + err := iVM.UpdateUserData(desc.UserData) + if err != nil { + log.Errorf("update userdata fail %s", err) + } + } + + err := iVM.DeployVM(ctx, desc.Name, desc.Password, desc.PublicKey, deleteKeypair, desc.Description) + if err != nil { + return nil, err + } + + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) + + return data, nil + }) + } else if action == "rebuild" { + + iVM, err := ihost.GetIVMById(guest.GetExternalId()) + if err != nil || iVM == nil { + log.Errorf("cannot find vm %s", err) + return fmt.Errorf("cannot find vm") + } + + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if len(desc.UserData) > 0 { + err := iVM.UpdateUserData(desc.UserData) + if err != nil { + log.Errorf("update userdata fail %s", err) + } + } + + diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, desc.Password, desc.PublicKey, desc.SysDisk.SizeGB) + if err != nil { + return nil, err + } + + log.Debugf("VMrebuildRoot %s new diskID %s, wait status ready ...", iVM.GetGlobalId(), diskId) + + err = cloudprovider.WaitStatus(iVM, models.VM_READY, time.Second*5, time.Second*1800) + if err != nil { + return nil, err + } + log.Debugf("VMrebuildRoot %s, and status is ready", iVM.GetGlobalId()) + + maxWaitSecs := 300 + waited := 0 + + for { + // hack, wait disk number consistent + idisks, err := iVM.GetIDisks() + if err != nil { + log.Errorf("fail to find VM idisks %s", err) + return nil, err + } + if len(idisks) < len(desc.DataDisks)+1 || idisks[0].GetGlobalId() != diskId { + if waited > maxWaitSecs { + log.Errorf("inconsistent disk number, wait timeout, must be something wrong on remote") + return nil, cloudprovider.ErrTimeout + } + if len(idisks) < len(desc.DataDisks)+1 { + log.Debugf("inconsistent disk number???? %d != %d", len(idisks), len(desc.DataDisks)+1) + } + if len(idisks) > 0 && idisks[0].GetGlobalId() != diskId { + log.Errorf("system disk id inconsistent %s != %s", idisks[0].GetGlobalId(), diskId) + } + time.Sleep(time.Second * 5) + waited += 5 + } else { + break + } + } + + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) + + return data, nil + }) + + } else { + log.Errorf("RequestDeployGuestOnHost: Action %s not supported", action) + return fmt.Errorf("Action %s not supported", action) + } + return nil } func (self *SHuaweiGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { - // todo: implement me + months := bc.GetMonths() + if (months >= 1 && months <= 9) || (months == 12) || (months == 24) || (months == 36) { + return true + } + return false } diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index 1624d37548..22bd714317 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -13,6 +13,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/httputils" ) @@ -30,6 +31,14 @@ func (self *SKVMGuestDriver) GetHypervisor() string { return models.HYPERVISOR_KVM } +func (self *SKVMGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_LOCAL +} + +func (self *SKVMGuestDriver) GetMinimalSysDiskSizeGb() int { + return options.Options.DefaultDiskSizeMB / 1024 +} + func (self *SKVMGuestDriver) RequestDetachDisksFromGuestForDelete(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { subtask, err := taskman.TaskManager.NewTask(ctx, "GuestDetachAllDisksTask", guest, task.GetUserCred(), task.GetParams(), task.GetTaskId(), "", nil) if err != nil { @@ -179,12 +188,16 @@ func (self *SKVMGuestDriver) RequestUndeployGuestOnHost(ctx context.Context, gue return nil } -func (self *SKVMGuestDriver) GetJsonDescAtHost(ctx context.Context, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { +func (self *SKVMGuestDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { return guest.GetJsonDescAtHypervisor(ctx, host) } func (self *SKVMGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } log.Debugf("RequestDeployGuestOnHost: %s", config) if config.Contains("container") { // ... @@ -211,7 +224,7 @@ func (self *SKVMGuestDriver) RequestStartOnHost(ctx context.Context, guest *mode header := self.getTaskRequestHeader(task) config := jsonutils.NewDict() - desc := guest.GetDriver().GetJsonDescAtHost(ctx, guest, host) + desc := guest.GetDriver().GetJsonDescAtHost(ctx, userCred, guest, host) config.Add(desc, "desc") params := task.GetParams() if params.Length() > 0 { @@ -242,8 +255,9 @@ func (self *SKVMGuestDriver) OnDeleteGuestFinalCleanup(ctx context.Context, gues return nil } -func (self *SKVMGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, vcpuCount, vmemSize int64) error { +func (self *SKVMGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, instanceType string, vcpuCount, vmemSize int64) error { // pass + task.ScheduleRun(nil) return nil } @@ -273,11 +287,11 @@ func (self *SKVMGuestDriver) GetRebuildRootStatus() ([]string, error) { } func (self *SKVMGuestDriver) GetChangeConfigStatus() ([]string, error) { - return []string{models.VM_READY}, nil + return []string{models.VM_READY, models.VM_RUNNING}, nil } func (self *SKVMGuestDriver) GetDeployStatus() ([]string, error) { - return []string{models.VM_READY, models.VM_RUNNING, models.VM_ADMIN}, nil + return []string{models.VM_READY, models.VM_ADMIN}, nil } func (self *SKVMGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *models.SDisk, storage *models.SStorage) error { @@ -288,7 +302,7 @@ func (self *SKVMGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *mode } func (self *SKVMGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - desc := guest.GetDriver().GetJsonDescAtHost(ctx, guest, host) + desc := guest.GetDriver().GetJsonDescAtHost(ctx, task.GetUserCred(), guest, host) body := jsonutils.NewDict() body.Add(desc, "desc") if fw_only, _ := task.GetParams().Bool("fw_only"); fw_only { diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 9edf528541..6e45485452 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -8,7 +8,6 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/util/compare" - "yunion.io/x/pkg/util/secrules" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" @@ -23,33 +22,11 @@ type SManagedVirtualizedGuestDriver struct { SVirtualizedGuestDriver } -type SManagedVMCreateConfig struct { - Name string - ExternalImageId string - OsDistribution string - OsVersion string - InstanceType string // InstanceType 不为空时,直接采用InstanceType创建机器。 - Cpu int - Memory int - ExternalNetworkId string - IpAddr string - Description string - StorageType string - SysDiskSize int - DataDisks []int - PublicKey string - SecGroupId string - SecGroupName string - SecRules []secrules.SecurityRule - - BillingCycle billing.SBillingCycle -} - -func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Context, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { - config := SManagedVMCreateConfig{} +func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost) jsonutils.JSONObject { + config := cloudprovider.SManagedVMCreateConfig{} config.Name = guest.Name config.Cpu = int(guest.VcpuCount) - config.Memory = guest.VmemSize + config.MemoryMB = guest.VmemSize config.Description = guest.Description config.InstanceType = guest.InstanceType @@ -58,23 +35,22 @@ func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Contex config.PublicKey = guest.GetKeypairPublicKey() } - nics := guest.GetNetworks() - net := nics[0].GetNetwork() - config.ExternalNetworkId = net.ExternalId - config.IpAddr = nics[0].IpAddr - - config.SecGroupId = guest.SecgrpId - config.SecGroupName = guest.GetSecgroupName() - config.SecRules = guest.GetSecRules() + nics, _ := guest.GetNetworks("") + if len(nics) > 0 { + net := nics[0].GetNetwork() + config.ExternalNetworkId = net.ExternalId + config.IpAddr = nics[0].IpAddr + } disks := guest.GetDisks() - config.DataDisks = make([]int, len(disks)-1) + config.DataDisks = []cloudprovider.SDiskInfo{} for i := 0; i < len(disks); i += 1 { disk := disks[i].GetDisk() + storage := disk.GetStorage() if i == 0 { - storage := disk.GetStorage() - config.StorageType = storage.StorageType + config.SysDisk.StorageType = storage.StorageType + config.SysDisk.SizeGB = disk.DiskSize / 1024 cache := storage.GetStoragecache() imageId := disk.GetTemplateId() //避免因同步过来的instance没有对应的imagecache信息,重置密码时引发空指针访问 @@ -84,9 +60,12 @@ func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Contex config.OsDistribution, _ = img.Info.GetString("properties", "os_distribution") config.OsVersion, _ = img.Info.GetString("properties", "os_version") } - config.SysDiskSize = disk.DiskSize / 1024 // MB => GB } else { - config.DataDisks[i-1] = disk.DiskSize / 1024 // MB => GB + dataDisk := cloudprovider.SDiskInfo{ + SizeGB: disk.DiskSize / 1024, + StorageType: storage.StorageType, + } + config.DataDisks = append(config.DataDisks, dataDisk) } } @@ -95,7 +74,9 @@ func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Contex if err != nil { log.Errorf("fail to parse billing cycle %s: %s", guest.BillingCycle, err) } - config.BillingCycle = bc + if bc.IsValid() { + config.BillingCycle = &bc + } } return jsonutils.Marshal(&config) @@ -295,38 +276,25 @@ type SManagedVMChangeConfig struct { Memory int } -func (self *SManagedVirtualizedGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, vcpuCount, vmemSize int64) error { - config := SManagedVMChangeConfig{} - config.InstanceId = guest.GetExternalId() - if instanceType, err := task.GetParams().GetString("instance_type"); err == nil { - config.InstanceType = instanceType - } - - config.Cpu = int(vcpuCount) - config.Memory = int(vmemSize) +func (self *SManagedVirtualizedGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *models.SGuest, task taskman.ITask, instanceType string, vcpuCount, vmemSize int64) error { ihost, err := guest.GetHost().GetIHost() if err != nil { return err } - iVM, err := ihost.GetIVMById(config.InstanceId) + iVM, err := ihost.GetIVMById(guest.GetExternalId()) if err != nil { return err } - if int(guest.VcpuCount) != config.Cpu || guest.VmemSize != config.Memory { - if len(config.InstanceType) > 0 { - err = iVM.ChangeConfig2(ctx, config.InstanceType) - if err != nil { - return err - } + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if len(instanceType) > 0 { + return nil, iVM.ChangeConfig2(ctx, instanceType) } else { - err = iVM.ChangeConfig(ctx, config.Cpu, config.Memory) - if err != nil { - return err - } + return nil, iVM.ChangeConfig(ctx, int(vcpuCount), int(vmemSize)) } - } + }) + return nil } @@ -353,6 +321,11 @@ func (self *SManagedVirtualizedGuestDriver) RequestDiskSnapshot(ctx context.Cont func (self *SManagedVirtualizedGuestDriver) OnGuestDeployTaskDataReceived(ctx context.Context, guest *models.SGuest, task taskman.ITask, data jsonutils.JSONObject) error { + uuid, _ := data.GetString("uuid") + if len(uuid) > 0 { + guest.SetExternalId(uuid) + } + recycle := false if guest.IsPrepaidRecycle() { recycle = true @@ -367,7 +340,7 @@ func (self *SManagedVirtualizedGuestDriver) OnGuestDeployTaskDataReceived(ctx co disks := guest.GetDisks() if len(disks) != len(diskInfo) { - msg := fmt.Sprintf("inconsistent disk number: have %d want %d", len(disks), len(diskInfo)) + msg := fmt.Sprintf("inconsistent disk number: guest have %d disks, data contains %d disks", len(disks), len(diskInfo)) log.Errorf(msg) return fmt.Errorf(msg) } @@ -419,10 +392,6 @@ func (self *SManagedVirtualizedGuestDriver) OnGuestDeployTaskDataReceived(ctx co } } } - uuid, _ := data.GetString("uuid") - if len(uuid) > 0 { - guest.SetExternalId(uuid) - } if metaData, _ := data.Get("metadata"); metaData != nil { meta := make(map[string]string, 0) @@ -459,7 +428,11 @@ func (self *SManagedVirtualizedGuestDriver) RequestSyncConfigOnHost(ctx context. if fwOnly, _ := task.GetParams().Bool("fw_only"); fwOnly { vpcId := "" - for _, network := range guest.GetNetworks() { + guestnets, err := guest.GetNetworks("") + if err != nil { + return nil, err + } + for _, network := range guestnets { if vpc := network.GetNetwork().GetVpc(); vpc != nil { vpcId = vpc.ExternalId break @@ -586,3 +559,7 @@ func (self *SManagedVirtualizedGuestDriver) RequestRenewInstance(guest *models.S } return iVM.GetExpiredAt(), nil } + +func (self *SManagedVirtualizedGuestDriver) IsSupportEip() bool { + return true +} diff --git a/pkg/compute/guestdrivers/openstack.go b/pkg/compute/guestdrivers/openstack.go index 98775e6f70..fd3b75e4dc 100644 --- a/pkg/compute/guestdrivers/openstack.go +++ b/pkg/compute/guestdrivers/openstack.go @@ -2,6 +2,7 @@ package guestdrivers import ( "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/compute/options" ) type SOpenStackGuestDriver struct { @@ -16,3 +17,15 @@ func init() { func (self *SOpenStackGuestDriver) GetHypervisor() string { return models.HYPERVISOR_OPENSTACK } + +func (self *SOpenStackGuestDriver) IsSupportEip() bool { + return false +} + +func (self *SOpenStackGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_OPENSTACK_ISCSI +} + +func (self *SOpenStackGuestDriver) GetMinimalSysDiskSizeGb() int { + return options.Options.DefaultDiskSizeMB / 1024 +} diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index e816c9e248..93f92215b3 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -17,7 +17,6 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/billing" - "yunion.io/x/onecloud/pkg/util/seclib2" ) type SQcloudGuestDriver struct { @@ -33,6 +32,14 @@ func (self *SQcloudGuestDriver) GetHypervisor() string { return models.HYPERVISOR_QCLOUD } +func (self *SQcloudGuestDriver) GetDefaultSysDiskBackend() string { + return models.STORAGE_CLOUD_BASIC +} + +func (self *SQcloudGuestDriver) GetMinimalSysDiskSizeGb() int { + return 50 +} + func (self *SQcloudGuestDriver) ChooseHostStorage(host *models.SHost, backend string) *models.SStorage { storages := host.GetAttachedStorages("") for i := 0; i < len(storages); i++ { @@ -40,7 +47,13 @@ func (self *SQcloudGuestDriver) ChooseHostStorage(host *models.SHost, backend st return &storages[i] } } - for _, stype := range []string{"cloud_basic", "cloud_premium", "cloud_ssd", "local_basic", "local_ssd"} { + for _, stype := range []string{ + models.STORAGE_CLOUD_BASIC, + models.STORAGE_CLOUD_PREMIUM, + models.STORAGE_CLOUD_SSD, + models.STORAGE_LOCAL_BASIC, + models.STORAGE_LOCAL_SSD, + } { for i := 0; i < len(storages); i++ { if storages[i].StorageType == stype { return &storages[i] @@ -110,34 +123,50 @@ func (self *SQcloudGuestDriver) ValidateCreateData(ctx context.Context, userCred return nil, fmt.Errorf("The %s system disk size must be less than 1024GB", sysDisk.Backend) } } + + for i := 1; data.Contains(fmt.Sprintf("disk.%d", i)); i++ { + disk := models.SDiskConfig{} + if err := data.Unmarshal(&disk, fmt.Sprintf("disk.%d", i)); err != nil { + return nil, httperrors.NewInputParameterError("invalid diskinfo of index %d", i) + } + switch disk.Backend { + case models.STORAGE_CLOUD_BASIC: + if disk.SizeMb < 10*1024 || disk.SizeMb > 16000*1024 { + return nil, httperrors.NewInputParameterError("The %s disk size must be in the range of 10GB ~ 16000GB", disk.Backend) + } + case models.STORAGE_CLOUD_PREMIUM: + if disk.SizeMb < 50*1024 || disk.SizeMb > 16000*1024 { + return nil, httperrors.NewInputParameterError("The %s disk size must be in the range of 50GB ~ 16000GB", disk.Backend) + } + case models.STORAGE_CLOUD_SSD: + if disk.SizeMb < 100*1024 || disk.SizeMb > 16000*1024 { + return nil, httperrors.NewInputParameterError("The %s disk size must be in the range of 100GB ~ 16000GB", disk.Backend) + } + } + } return data, nil } func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams()) + config, err := guest.GetDeployConfigOnHost(ctx, task.GetUserCred(), host, task.GetParams()) + if err != nil { + log.Errorf("GetDeployConfigOnHost error: %v", err) + return err + } log.Debugf("RequestDeployGuestOnHost: %s", config) /* onfinish, err := config.GetString("on_finish") if err != nil { return err } */ - action, err := config.GetString("action") - if err != nil { + desc := cloudprovider.SManagedVMCreateConfig{} + if err := desc.GetConfig(config); err != nil { return err } - publicKey, _ := config.GetString("public_key") - - adminPublicKey, _ := config.GetString("admin_public_key") - projectPublicKey, _ := config.GetString("project_public_key") - oUserData, _ := config.GetString("user_data") - - userData := generateUserData(adminPublicKey, projectPublicKey, oUserData) - - resetPassword := jsonutils.QueryBoolean(config, "reset_password", false) - passwd, _ := config.GetString("password") - if resetPassword && len(passwd) == 0 { - passwd = seclib2.RandomPassword2(12) + action, err := config.GetString("action") + if err != nil { + return err } ihost, err := host.GetIHost() @@ -145,52 +174,10 @@ func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu return err } - desc := SManagedVMCreateConfig{} - err = config.Unmarshal(&desc, "desc") - if err != nil { - return err - } - if action == "create" { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - nets := guest.GetNetworks() - net := nets[0].GetNetwork() - vpc := net.GetVpc() - - iregion, err := host.GetIRegion() - if err != nil { - return nil, err - } - - secgroupCache := models.SecurityGroupCacheManager.Register(ctx, task.GetUserCred(), desc.SecGroupId, "normal", vpc.CloudregionId, vpc.ManagerId) - if secgroupCache == nil { - return nil, fmt.Errorf("failed to registor secgroupCache for secgroup: %s, vpc: %s", desc.SecGroupId, vpc.Name) - } - - secgroupExtId, err := iregion.SyncSecurityGroup(secgroupCache.ExternalId, vpc.ExternalId, desc.SecGroupName, "", desc.SecRules) - if err != nil { - log.Errorf("SyncSecurityGroup fail %s", err) - return nil, err - } - if err := secgroupCache.SetExternalId(secgroupExtId); err != nil { - return nil, fmt.Errorf("failed to set externalId for secgroup %s externalId %s: error: %v", desc.SecGroupId, secgroupExtId, err) - } - - var createErr error - var iVM cloudprovider.ICloudVM - var bc *billing.SBillingCycle - if desc.BillingCycle.IsValid() { - bc = &desc.BillingCycle - } - if len(desc.InstanceType) > 0 { - iVM, createErr = ihost.CreateVM2(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.InstanceType, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, bc) - } else { - iVM, createErr = ihost.CreateVM(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.Cpu, desc.Memory, desc.ExternalNetworkId, - desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, bc) - } - + iVM, createErr := ihost.CreateVM(&desc) if createErr != nil { return nil, createErr } @@ -200,14 +187,31 @@ func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu if err != nil { return nil, err } - log.Debugf("VMcreated %s, and status is ready", iVM.GetGlobalId()) + log.Debugf("VMcreated %s, and status is running", iVM.GetGlobalId()) iVM, err = ihost.GetIVMById(iVM.GetGlobalId()) if err != nil { log.Errorf("cannot find vm %s", err) return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + + err = cloudprovider.RetryUntil(func() (bool, error) { + idisks, err := iVM.GetIDisks() + if err != nil { + log.Errorf("cannot find vm disks %s", err) + return false, err + } + if len(idisks) == len(desc.DataDisks)+1 { + return true, nil + } else { + return false, nil + } + }, 10) + if err != nil { + return nil, err + } + + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) } else if action == "deploy" { @@ -220,9 +224,6 @@ func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu params := task.GetParams() log.Debugf("Deploy VM params %s", params.String()) - name, _ := params.GetString("name") - description, _ := params.GetString("description") - publicKey, _ := config.GetString("public_key") deleteKeypair := jsonutils.QueryBoolean(params, "__delete_keypair__", false) taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { @@ -234,12 +235,12 @@ func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu // } // } - err := iVM.DeployVM(ctx, name, passwd, publicKey, deleteKeypair, description) + err := iVM.DeployVM(ctx, desc.Name, desc.Password, desc.PublicKey, deleteKeypair, desc.Description) if err != nil { return nil, err } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) } else if action == "rebuild" { @@ -259,7 +260,7 @@ func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu // } // } - diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, passwd, publicKey, desc.SysDiskSize) + diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, desc.Password, desc.PublicKey, desc.SysDisk.SizeGB) if err != nil { return nil, err } @@ -300,7 +301,7 @@ func (self *SQcloudGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu } } - data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action) + data := fetchIVMinfo(desc, iVM, guest.Id, "root", desc.Password, action) return data, nil }) diff --git a/pkg/compute/guestdrivers/utils.go b/pkg/compute/guestdrivers/utils.go index b2413a8a06..f656e8b5ea 100644 --- a/pkg/compute/guestdrivers/utils.go +++ b/pkg/compute/guestdrivers/utils.go @@ -28,7 +28,7 @@ type SDiskInfo struct { Metadata map[string]string } -func fetchIVMinfo(desc SManagedVMCreateConfig, iVM cloudprovider.ICloudVM, guestId string, account, passwd string, action string) *jsonutils.JSONDict { +func fetchIVMinfo(desc cloudprovider.SManagedVMCreateConfig, iVM cloudprovider.ICloudVM, guestId string, account, passwd string, action string) *jsonutils.JSONDict { data := jsonutils.NewDict() data.Add(jsonutils.NewString(iVM.GetOSType()), "os") diff --git a/pkg/compute/guestdrivers/virtualization.go b/pkg/compute/guestdrivers/virtualization.go index 3f0d8ee7ae..9215b36ce2 100644 --- a/pkg/compute/guestdrivers/virtualization.go +++ b/pkg/compute/guestdrivers/virtualization.go @@ -38,7 +38,7 @@ func (self *SVirtualizedGuestDriver) GetNamedNetworkConfiguration(guest *models. } func (self *SVirtualizedGuestDriver) GetRandomNetworkTypes() []string { - return []string{models.SERVER_TYPE_GUEST} + return []string{models.NETWORK_TYPE_GUEST} } func (self *SVirtualizedGuestDriver) Attach2RandomNetwork(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, netConfig *models.SNetworkConfig, pendingUsage quotas.IQuota) error { @@ -49,6 +49,9 @@ func (self *SVirtualizedGuestDriver) Attach2RandomNetwork(guest *models.SGuest, hostwires := host.GetHostwires() netsAvaiable := make([]models.SNetwork, 0) netTypes := guest.GetDriver().GetRandomNetworkTypes() + if len(netConfig.NetType) > 0 { + netTypes = []string{netConfig.NetType} + } for i := 0; i < len(hostwires); i += 1 { hostwire := hostwires[i] wire := hostwire.GetWire() @@ -67,19 +70,9 @@ func (self *SVirtualizedGuestDriver) Attach2RandomNetwork(guest *models.SGuest, var net *models.SNetwork if netConfig.Private { - for _, netType := range netTypes { - net, _ = wire.GetCandidatePrivateNetwork(userCred, netConfig.Exit, netType) - if net != nil { - break - } - } + net, _ = wire.GetCandidatePrivateNetwork(userCred, netConfig.Exit, netTypes) } else { - for _, netType := range netTypes { - net, _ = wire.GetCandidatePublicNetwork(netConfig.Exit, netType) - if net != nil { - break - } - } + net, _ = wire.GetCandidatePublicNetwork(netConfig.Exit, netTypes) } if net != nil { netsAvaiable = append(netsAvaiable, *net) @@ -88,17 +81,11 @@ func (self *SVirtualizedGuestDriver) Attach2RandomNetwork(guest *models.SGuest, if len(netsAvaiable) == 0 { return fmt.Errorf("No appropriate host virtual network...") } - var selNet *models.SNetwork - for _, netType := range netTypes { - selNet = models.ChooseCandidateNetworks(netsAvaiable, netConfig.Exit, netType) - if selNet != nil { - break - } - } + selNet := models.ChooseCandidateNetworks(netsAvaiable, netConfig.Exit, netTypes) if selNet == nil { return fmt.Errorf("Not enough address in virtual network") } - err := guest.Attach2Network(ctx, userCred, selNet, pendingUsage, netConfig.Address, netConfig.Mac, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, -1, netConfig.Reserved, models.IPAllocationDefault, false) + err := guest.Attach2Network(ctx, userCred, selNet, pendingUsage, netConfig.Address, netConfig.Mac, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, -1, netConfig.Reserved, models.IPAllocationDefault, false, netConfig.Ifname) return err } @@ -132,17 +119,6 @@ func (self *SVirtualizedGuestDriver) StartGuestResetTask(guest *models.SGuest, c return nil } -func (self *SVirtualizedGuestDriver) StartGuestRestartTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { - data := jsonutils.NewDict() - data.Set("is_force", jsonutils.NewBool(isForce)) - task, err := taskman.TaskManager.NewTask(ctx, "GuestRestartTask", guest, userCred, nil, parentTaskId, "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - return nil -} - func (self *SVirtualizedGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, disk *models.SDisk, task taskman.ITask, isPurge bool) error { return disk.StartDiskDeleteTask(ctx, task.GetUserCred(), task.GetTaskId(), isPurge, jsonutils.QueryBoolean(task.GetParams(), "override_pending_delete", false)) diff --git a/pkg/compute/hostdrivers/baremetal.go b/pkg/compute/hostdrivers/baremetal.go new file mode 100644 index 0000000000..2afc8c086f --- /dev/null +++ b/pkg/compute/hostdrivers/baremetal.go @@ -0,0 +1,56 @@ +package hostdrivers + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SBaremetalHostDriver struct { + SBaseHostDriver +} + +func init() { + driver := SBaremetalHostDriver{} + models.RegisterHostDriver(&driver) +} + +func (self *SBaremetalHostDriver) GetHostType() string { + return models.HOST_TYPE_BAREMETAL +} + +func (self *SBaremetalHostDriver) CheckAndSetCacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestAllocateDiskOnStorage(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, task taskman.ITask, content *jsonutils.JSONDict) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestDeallocateDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, task taskman.ITask) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestPrepareSaveDiskOnHost(ctx context.Context, host *models.SHost, disk *models.SDisk, imageId string, task taskman.ITask) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestRebuildDiskOnStorage(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, task taskman.ITask, content *jsonutils.JSONDict) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestResizeDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, guest *models.SGuest, sizeMb int64, task taskman.ITask) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestSaveUploadImageOnHost(ctx context.Context, host *models.SHost, disk *models.SDisk, imageId string, task taskman.ITask, data jsonutils.JSONObject) error { + return fmt.Errorf("not supported") +} + +func (self *SBaremetalHostDriver) RequestUncacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error { + return fmt.Errorf("not supported") +} diff --git a/pkg/compute/hostdrivers/base.go b/pkg/compute/hostdrivers/base.go index 9adc40c7bc..e215c3c908 100644 --- a/pkg/compute/hostdrivers/base.go +++ b/pkg/compute/hostdrivers/base.go @@ -172,3 +172,15 @@ func (self *SBaseHostDriver) GetRaidScheme(host *models.SHost, raid string) (str } return raid, nil } + +func (driver *SBaseHostDriver) IsReachStoragecacheCapacityLimit(host *models.SHost, cachedImages []models.SCachedimage) bool { + return false +} + +func (driver *SBaseHostDriver) GetStoragecacheQuota(host *models.SHost) int { + return -1 +} + +func (driver *SBaseHostDriver) RequestDeallocateBackupDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, task taskman.ITask) error { + return fmt.Errorf("Not Implement") +} diff --git a/pkg/compute/hostdrivers/kvm.go b/pkg/compute/hostdrivers/kvm.go index fb028d7705..80401c164f 100644 --- a/pkg/compute/hostdrivers/kvm.go +++ b/pkg/compute/hostdrivers/kvm.go @@ -163,33 +163,56 @@ func (self *SKVMHostDriver) RequestDeallocateDiskOnHost(ctx context.Context, hos return err } -func (self *SKVMHostDriver) RequestResizeDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, sizeMb int64, task taskman.ITask) error { +func (driver *SKVMHostDriver) RequestDeallocateBackupDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, task taskman.ITask) error { + log.Infof("Deallocating disk on host %s", host.GetName()) + header := mcclient.GetTokenHeaders(task.GetUserCred()) + url := fmt.Sprintf("/disks/%s/delete/%s", storage.Id, disk.Id) + body := jsonutils.NewDict() + _, err := host.Request(ctx, task.GetUserCred(), "POST", url, header, body) + return err +} + +func (self *SKVMHostDriver) RequestResizeDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, guest *models.SGuest, sizeMb int64, task taskman.ITask) error { header := task.GetTaskRequestHeader() url := fmt.Sprintf("/disks/%s/resize/%s", storage.Id, disk.Id) body := jsonutils.NewDict() content := jsonutils.NewDict() content.Add(jsonutils.NewInt(sizeMb), "size") + if guest != nil { + content.Add(jsonutils.NewString(guest.Id), "server_id") + } body.Add(content, "disk") _, err := host.Request(ctx, task.GetUserCred(), "POST", url, header, body) return err } +/* func (self *SKVMHostDriver) RequestResizeDiskOnHostOnline(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, sizeMb int64, task taskman.ITask) error { - self.RequestResizeDiskOnHost(ctx, host, storage, disk, sizeMb, task) - header := task.GetTaskRequestHeader() - for _, guest := range disk.GetAttachedGuests() { - guestdisk := guest.GetGuestDisk(disk.GetId()) - url := fmt.Sprintf("/servers/%s/monitor", guest.GetId()) - body := jsonutils.NewDict() - cmd := fmt.Sprintf("block_resize drive_%d %dM", guestdisk.Index, sizeMb) - body.Add(jsonutils.NewString(cmd), "cmd") - host.Request(ctx, task.GetUserCred(), "POST", url, header, body) + guests := disk.GetAttachedGuests() + if len(guests) == 0 { + return fmt.Errorf("no valid guest") } + if len(guests) > 1 { + return fmt.Errorf("cannot resize across more than 1 guest") + } + guest := guests[0] + + guestdisk := guest.GetGuestDisk(disk.GetId()) + url := fmt.Sprintf("/servers/%s/monitor", guest.GetId()) + body := jsonutils.NewDict() + cmd := fmt.Sprintf("block_resize drive_%d %dM", guestdisk.Index, sizeMb) + body.Add(jsonutils.NewString(cmd), "cmd") + + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + return host.Request(ctx, task.GetUserCred(), "POST", url, header, body) + }) + return nil } +*/ func (self *SKVMHostDriver) RequestPrepareSaveDiskOnHost(ctx context.Context, host *models.SHost, disk *models.SDisk, imageId string, task taskman.ITask) error { body := jsonutils.NewDict() diff --git a/pkg/compute/hostdrivers/managedvirtual.go b/pkg/compute/hostdrivers/managedvirtual.go index 43ac28e46d..331010df04 100644 --- a/pkg/compute/hostdrivers/managedvirtual.go +++ b/pkg/compute/hostdrivers/managedvirtual.go @@ -30,7 +30,13 @@ func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context. osArch, _ := params.GetString("os_arch") osType, _ := params.GetString("os_type") osDist, _ := params.GetString("os_distribution") - osVersion, _ := params.GetString("os_version") + var osVersion string + providerName := storageCache.GetProviderName() + if providerName == models.CLOUD_PROVIDER_HUAWEI { + osVersion, _ = params.GetString("os_full_version") + } else { + osVersion, _ = params.GetString("os_version") + } isForce := jsonutils.QueryBoolean(params, "is_force", false) userCred := task.GetUserCred() @@ -39,14 +45,29 @@ func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context. lockman.LockRawObject(ctx, "cachedimages", fmt.Sprintf("%s-%s", storageCache.Id, imageId)) defer lockman.ReleaseRawObject(ctx, "cachedimages", fmt.Sprintf("%s-%s", storageCache.Id, imageId)) - scimg := models.StoragecachedimageManager.Register(ctx, task.GetUserCred(), storageCache.Id, imageId) + scimg := models.StoragecachedimageManager.Register(ctx, task.GetUserCred(), storageCache.Id, imageId, "") + + cachedImage := scimg.GetCachedimage() + if cachedImage == nil { + return nil, fmt.Errorf("cached image not found???") + } iStorageCache, err := storageCache.GetIStorageCache() if err != nil { return nil, err } - extImgId, err := iStorageCache.UploadImage(ctx, userCred, imageId, osArch, osType, osDist, osVersion, scimg.ExternalId, isForce) + var extImgId string + if cachedImage.ImageType == cloudprovider.CachedImageTypeCustomized { + extImgId, err = iStorageCache.UploadImage(ctx, userCred, imageId, osArch, osType, osDist, osVersion, scimg.ExternalId, isForce) + } else { + _, err = iStorageCache.GetIImageById(cachedImage.ExternalId) + if err != nil { + log.Errorf("remote image fetch error %s", err) + return nil, err + } + extImgId = cachedImage.ExternalId + } if err != nil { return nil, err @@ -68,7 +89,7 @@ func (self *SManagedVirtualizationHostDriver) RequestUncacheImage(ctx context.Co return err } - scimg := models.StoragecachedimageManager.Register(ctx, task.GetUserCred(), storageCache.Id, imageId) + scimg := models.StoragecachedimageManager.Register(ctx, task.GetUserCred(), storageCache.Id, imageId, "") if scimg == nil { task.ScheduleRun(nil) return nil @@ -136,7 +157,7 @@ func (self *SManagedVirtualizationHostDriver) RequestSaveUploadImageOnHost(ctx c params := task.GetParams() osType, _ := params.GetString("properties", "os_type") - scimg := models.StoragecachedimageManager.Register(ctx, task.GetUserCred(), iStoragecache.GetId(), imageId) + scimg := models.StoragecachedimageManager.Register(ctx, task.GetUserCred(), iStoragecache.GetId(), imageId, "") if scimg.Status != models.CACHED_IMAGE_STATUS_READY { scimg.SetStatus(task.GetUserCred(), models.CACHED_IMAGE_STATUS_CACHING, "request_prepare_save_disk_on_host") } @@ -169,11 +190,7 @@ func (self *SManagedVirtualizationHostDriver) RequestSaveUploadImageOnHost(ctx c return nil } -func (self *SManagedVirtualizationHostDriver) RequestResizeDiskOnHostOnline(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, size int64, task taskman.ITask) error { - return self.RequestResizeDiskOnHost(ctx, host, storage, disk, size, task) -} - -func (self *SManagedVirtualizationHostDriver) RequestResizeDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, sizeMb int64, task taskman.ITask) error { +func (self *SManagedVirtualizationHostDriver) RequestResizeDiskOnHost(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, guest *models.SGuest, sizeMb int64, task taskman.ITask) error { iCloudStorage, err := storage.GetIStorage() if err != nil { log.Errorf("storage.GetIStorage fail %s", err) @@ -311,3 +328,11 @@ func (self *SManagedVirtualizationHostDriver) RequestRebuildDiskOnStorage(ctx co }) return nil } + +func (driver *SManagedVirtualizationHostDriver) IsReachStoragecacheCapacityLimit(host *models.SHost, cachedImages []models.SCachedimage) bool { + quota := host.GetHostDriver().GetStoragecacheQuota(host) + if quota > 0 && len(cachedImages) >= quota { + return true + } + return false +} diff --git a/pkg/compute/hostdrivers/qcloud.go b/pkg/compute/hostdrivers/qcloud.go index d5f83cd43e..8244f8b604 100644 --- a/pkg/compute/hostdrivers/qcloud.go +++ b/pkg/compute/hostdrivers/qcloud.go @@ -47,3 +47,7 @@ func (self *SQcloudHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb func (self *SQcloudHostDriver) RequestDeleteSnapshotWithStorage(ctx context.Context, host *models.SHost, snapshot *models.SSnapshot, task taskman.ITask) error { return httperrors.NewNotImplementedError("not implement") } + +func (driver *SQcloudHostDriver) GetStoragecacheQuota(host *models.SHost) int { + return 10 +} diff --git a/pkg/compute/models/baremetalagents.go b/pkg/compute/models/baremetalagents.go index 0a48d57e4f..65fcf1c30b 100644 --- a/pkg/compute/models/baremetalagents.go +++ b/pkg/compute/models/baremetalagents.go @@ -17,6 +17,12 @@ const ( BAREMETAL_AGENT_OFFLINE = "offline" ) +const ( + AgentTypeBaremetal = "baremetal" + AgentTypeEsxi = "esxiagent" + AgentTypeDefault = AgentTypeBaremetal +) + type SBaremetalagentManager struct { db.SStandaloneResourceBaseManager } @@ -79,14 +85,6 @@ func (self *SBaremetalagent) ValidateUpdateData(ctx context.Context, userCred mc return nil, httperrors.NewConflictError("Conflict manager_uri %s", mangerUri) } } - accessIp, err := data.GetString("access_ip") - if err == nil { - count := BaremetalagentManager.Query().Equals("access_ip", accessIp). - NotEquals("id", self.Id).Count() - if count > 0 { - return nil, httperrors.NewConflictError("Conflict access_ip %s", accessIp) - } - } return self.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) } @@ -96,11 +94,6 @@ func (manager *SBaremetalagentManager) ValidateCreateData(ctx context.Context, u if count > 0 { return nil, httperrors.NewDuplicateResourceError("Duplicate manager_uri %s", mangerUri) } - //accessIp, _ := data.GetString("access_ip") - //count = manager.Query().Equals("access_ip", accessIp).Count() - //if count > 0 { - // return nil, httperrors.NewDuplicateResourceError("Duplicate access_ip %s", accessIp) - //} return manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) } diff --git a/pkg/compute/models/cachedimages.go b/pkg/compute/models/cachedimages.go index 6b77db2a71..d634f37f81 100644 --- a/pkg/compute/models/cachedimages.go +++ b/pkg/compute/models/cachedimages.go @@ -9,16 +9,17 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" ) const ( @@ -45,13 +46,17 @@ func init() { type SCachedimage struct { db.SStandaloneResourceBase + // SManagedResourceBase - Size int64 `nullable:"false" list:"admin" update:"admin" create:"admin_required"` // = Column(BigInteger, nullable=False) # in Byte + Size int64 `nullable:"false" list:"user" update:"admin" create:"admin_required"` // = Column(BigInteger, nullable=False) # in Byte // virtual_size = Column(BigInteger, nullable=False) # in Byte - Info jsonutils.JSONObject `nullable:"true" get:"admin" update:"admin" create:"admin_required"` // Column(JSONEncodedDict, nullable=True) - LastSync time.Time `list:"admin"` // = Column(DateTime) - LastRef time.Time `list:"admin"` // = Column(DateTime) - RefCount int `default:"0" list:"admin"` // = Column(Integer, default=0, server_default='0') + Info jsonutils.JSONObject `nullable:"true" list:"user" update:"admin" create:"admin_required"` // Column(JSONEncodedDict, nullable=True) + + LastSync time.Time `list:"admin"` // = Column(DateTime) + LastRef time.Time `list:"admin"` // = Column(DateTime) + RefCount int `default:"0" list:"user"` // = Column(Integer, default=0, server_default='0') + + ImageType string `width:"16" default:"customized" list:"user"` } func (self *SCachedimageManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { @@ -93,6 +98,9 @@ func (self *SCachedimage) isReferenceSessionExpire() bool { } func (self *SCachedimage) isRefreshSessionExpire() bool { + if len(self.ExternalId) > 0 { // external image info never expires + return false + } if !self.LastRef.IsZero() && time.Now().Sub(self.LastRef) < CACHED_IMAGE_REFRESH_SECONDS*time.Second { return false } else { @@ -134,18 +142,20 @@ func (self *SCachedimage) getStoragecacheCount() int { return self.getStoragecacheQuery().Count() } -func (self *SCachedimage) GetImage() (*SImage, error) { - image := SImage{} +func (self *SCachedimage) GetImage() (*cloudprovider.SImage, error) { + image := cloudprovider.SImage{} err := self.Info.Unmarshal(&image) if err != nil { return nil, err } else { + // hack, make cached image ID consistent + image.Id = self.Id return &image, nil } } -func (manager *SCachedimageManager) cacheImageInfo(ctx context.Context, userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SCachedimage, error) { +func (manager *SCachedimageManager) cacheGlanceImageInfo(ctx context.Context, userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SCachedimage, error) { lockman.LockClass(ctx, manager, userCred.GetProjectId()) defer lockman.ReleaseClass(ctx, manager, userCred.GetProjectId()) @@ -158,12 +168,18 @@ func (manager *SCachedimageManager) cacheImageInfo(ctx context.Context, userCred imageCache.SetModelManager(manager) size, _ := info.Int("size") + name, _ := info.GetString("name") + if len(name) == 0 { + name = imgId + } + + name = db.GenerateName(manager, "", name) err := manager.Query().Equals("id", imgId).First(&imageCache) if err != nil { if err == sql.ErrNoRows { // insert imageCache.Id = imgId - imageCache.Name = imgId + imageCache.Name = name imageCache.Size = size imageCache.Info = info imageCache.LastSync = timeutils.UtcNow() @@ -196,14 +212,14 @@ func (manager *SCachedimageManager) cacheImageInfo(ctx context.Context, userCred } } -func (manager *SCachedimageManager) GetImageById(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*SImage, error) { - if !refresh { - imgObj, _ := manager.FetchById(imageId) - if imgObj != nil { - cachedImage := imgObj.(*SCachedimage) - if cachedImage.GetStatus() == "active" && len(cachedImage.GetOSType()) > 0 && cachedImage.isRefreshSessionExpire() { - return cachedImage.GetImage() - } +func (manager *SCachedimageManager) GetImageById(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*cloudprovider.SImage, error) { + imgObj, _ := manager.FetchById(imageId) + if imgObj != nil { + cachedImage := imgObj.(*SCachedimage) + if !refresh && cachedImage.GetStatus() == cloudprovider.IMAGE_STATUS_ACTIVE && len(cachedImage.GetOSType()) > 0 && cachedImage.isRefreshSessionExpire() { + return cachedImage.GetImage() + } else if len(cachedImage.ExternalId) > 0 { // external image, request refresh + return cachedImage.requestRefreshExternalImage(ctx, userCred) } } s := auth.GetAdminSession(ctx, options.Options.Region, "") @@ -212,40 +228,63 @@ func (manager *SCachedimageManager) GetImageById(ctx context.Context, userCred m log.Errorf("GetImageById %s error %s", imageId, err) return nil, err } - cachedImage, err := manager.cacheImageInfo(ctx, userCred, obj) + cachedImage, err := manager.cacheGlanceImageInfo(ctx, userCred, obj) if err != nil { return nil, err } return cachedImage.GetImage() } -func (manager *SCachedimageManager) getImageByName(ctx context.Context, userCred mcclient.TokenCredential, imageId string) (*SImage, error) { +func (manager *SCachedimageManager) getImageByName(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*cloudprovider.SImage, error) { + imgObj, _ := manager.FetchByName(userCred, imageId) + if imgObj != nil { + cachedImage := imgObj.(*SCachedimage) + if !refresh && cachedImage.GetStatus() == cloudprovider.IMAGE_STATUS_ACTIVE && len(cachedImage.GetOSType()) > 0 && cachedImage.isRefreshSessionExpire() { + return cachedImage.GetImage() + } else if len(cachedImage.ExternalId) > 0 { // external image, request refresh + return cachedImage.requestRefreshExternalImage(ctx, userCred) + } + } s := auth.GetSession(ctx, userCred, options.Options.Region, "") obj, err := modules.Images.GetByName(s, imageId, nil) if err != nil { return nil, err } - cachedImage, err := manager.cacheImageInfo(ctx, userCred, obj) + cachedImage, err := manager.cacheGlanceImageInfo(ctx, userCred, obj) if err != nil { return nil, err } return cachedImage.GetImage() } -func (manager *SCachedimageManager) getImageInfo(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*SImage, error) { +func (manager *SCachedimageManager) getImageInfo(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*cloudprovider.SImage, error) { img, err := manager.GetImageById(ctx, userCred, imageId, refresh) if err == nil { return img, nil } log.Errorf("getImageInfoById %s fail %s", imageId, err) - return manager.getImageByName(ctx, userCred, imageId) + return manager.getImageByName(ctx, userCred, imageId, refresh) +} + +func (self *SCachedimage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query) + if err != nil { + return nil, err + } + extra = self.getMoreDetails(extra) + return extra, nil } func (self *SCachedimage) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := self.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) - extra.Add(jsonutils.NewString(self.GetName()), "name") - extra.Add(jsonutils.NewString(self.GetOwner()), "owner") - extra.Add(jsonutils.NewString(self.GetFormat()), "format") + extra = self.getMoreDetails(extra) + return extra +} + +func (self *SCachedimage) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { + // extra.Add(jsonutils.NewString(self.GetName()), "name") + //extra.Add(jsonutils.NewString(self.GetOwner()), "owner") + //extra.Add(jsonutils.NewString(self.GetFormat()), "format") extra.Add(jsonutils.NewString(self.GetStatus()), "status") for _, k := range []string{"os_type", "os_distribution", "os_version", "hypervisor"} { val, _ := self.Info.GetString("properties", k) @@ -253,7 +292,7 @@ func (self *SCachedimage) GetCustomizeColumns(ctx context.Context, userCred mccl extra.Add(jsonutils.NewString(val), k) } } - extra.Add(jsonutils.NewInt(int64(self.getStoragecacheCount())), "storage_cache_count") + extra.Add(jsonutils.NewInt(int64(self.getStoragecacheCount())), "cached_count") return extra } @@ -346,3 +385,200 @@ func (self *SCachedimage) canDeleteLastCache() bool { } return false } + +func (self *SCachedimage) syncWithCloudImage(ctx context.Context, userCred mcclient.TokenCredential, image cloudprovider.ICloudImage) error { + diff, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.Name = image.GetName() + self.Size = image.GetSize() + self.ExternalId = image.GetGlobalId() + self.ImageType = image.GetImageType() + sImage := cloudprovider.CloudImage2Image(image) + self.Info = jsonutils.Marshal(&sImage) + self.LastSync = time.Now().UTC() + return nil + }) + db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred) + return err +} + +func (manager *SCachedimageManager) newFromCloudImage(ctx context.Context, userCred mcclient.TokenCredential, image cloudprovider.ICloudImage) (*SCachedimage, error) { + cachedImage := SCachedimage{} + cachedImage.SetModelManager(manager) + + cachedImage.Name = image.GetName() + cachedImage.Size = image.GetSize() + sImage := cloudprovider.CloudImage2Image(image) + cachedImage.Info = jsonutils.Marshal(&sImage) + cachedImage.LastSync = time.Now().UTC() + cachedImage.ImageType = image.GetImageType() + cachedImage.ExternalId = image.GetGlobalId() + + err := manager.TableSpec().Insert(&cachedImage) + if err != nil { + return nil, err + } + + return &cachedImage, nil +} + +func (image *SCachedimage) requestRefreshExternalImage(ctx context.Context, userCred mcclient.TokenCredential) (*cloudprovider.SImage, error) { + caches := image.getValidStoragecache() + if caches == nil || len(caches) == 0 { + return nil, fmt.Errorf("no valid storage cache") + } + iCache, err := caches[0].GetIStorageCache() + if err != nil { + return nil, err + } + iImage, err := iCache.GetIImageById(image.ExternalId) + if err != nil { + return nil, err + } + err = image.syncWithCloudImage(ctx, userCred, iImage) + if err != nil { + return nil, err + } + return image.GetImage() +} + +func (image *SCachedimage) getValidStoragecache() []SStoragecache { + storagecaches := StoragecacheManager.Query().SubQuery() + storagecacheimages := StoragecachedimageManager.Query().SubQuery() + providers := CloudproviderManager.Query().SubQuery() + + q := storagecaches.Query() + q = q.Join(providers, sqlchemy.Equals(providers.Field("id"), storagecaches.Field("manager_id"))) + q = q.Join(storagecacheimages, sqlchemy.Equals(storagecaches.Field("id"), storagecacheimages.Field("storagecache_id"))) + q = q.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) + q = q.Filter(sqlchemy.Equals(providers.Field("status"), CLOUD_PROVIDER_CONNECTED)) + q = q.Filter(sqlchemy.Equals(storagecacheimages.Field("cachedimage_id"), image.Id)) + + caches := make([]SStoragecache, 0) + err := db.FetchModelObjects(StoragecacheManager, q, &caches) + if err != nil { + log.Errorf("getValidStoragecache fail %s", err) + return nil + } + return caches +} + +func (manager *SCachedimageManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + + providerStr := jsonutils.GetAnyString(query, []string{"provider"}) + if len(providerStr) > 0 { + cachedImages := CachedimageManager.Query().SubQuery() + storagecachedImages := StoragecachedimageManager.Query().SubQuery() + storageCaches := StoragecacheManager.Query().SubQuery() + cloudproviders := CloudproviderManager.Query().SubQuery() + + subq := cachedImages.Query(cachedImages.Field("id")) + subq = subq.Join(storagecachedImages, sqlchemy.Equals(cachedImages.Field("id"), storagecachedImages.Field("cachedimage_id"))) + subq = subq.Join(storageCaches, sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), storageCaches.Field("id"))) + subq = subq.Join(cloudproviders, sqlchemy.Equals(storageCaches.Field("manager_id"), cloudproviders.Field("id"))) + subq = subq.Filter(sqlchemy.Equals(cloudproviders.Field("provider"), providerStr)) + + q = q.Filter(sqlchemy.In(q.Field("id"), subq.SubQuery())) + } + + accountStr := jsonutils.GetAnyString(query, []string{"account", "account_id"}) + if len(accountStr) > 0 { + accountObj, err := CloudaccountManager.FetchByIdOrName(nil, accountStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudaccountManager.Keyword(), accountStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + cachedImages := CachedimageManager.Query().SubQuery() + storagecachedImages := StoragecachedimageManager.Query().SubQuery() + storageCaches := StoragecacheManager.Query().SubQuery() + cloudproviders := CloudproviderManager.Query().SubQuery() + + subq := cachedImages.Query(cachedImages.Field("id")) + subq = subq.Join(storagecachedImages, sqlchemy.Equals(cachedImages.Field("id"), storagecachedImages.Field("cachedimage_id"))) + subq = subq.Join(storageCaches, sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), storageCaches.Field("id"))) + subq = subq.Join(cloudproviders, sqlchemy.Equals(storageCaches.Field("manager_id"), cloudproviders.Field("id"))) + subq = subq.Filter(sqlchemy.Equals(cloudproviders.Field("cloudaccount_id"), accountObj.GetId())) + + q = q.Filter(sqlchemy.In(q.Field("id"), subq.SubQuery())) + } + + managerStr := jsonutils.GetAnyString(query, []string{"manager", "manager_id"}) + if len(managerStr) > 0 { + managerObj, err := CloudproviderManager.FetchByIdOrName(nil, managerStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudproviderManager.Keyword(), managerStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + cachedImages := CachedimageManager.Query().SubQuery() + storagecachedImages := StoragecachedimageManager.Query().SubQuery() + storageCaches := StoragecacheManager.Query().SubQuery() + + subq := cachedImages.Query(cachedImages.Field("id")) + subq = subq.Join(storagecachedImages, sqlchemy.Equals(cachedImages.Field("id"), storagecachedImages.Field("cachedimage_id"))) + subq = subq.Join(storageCaches, sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), storageCaches.Field("id"))) + subq = subq.Filter(sqlchemy.Equals(storageCaches.Field("manager_id"), managerObj.GetId())) + + q = q.Filter(sqlchemy.In(q.Field("id"), subq.SubQuery())) + } + + regionStr := jsonutils.GetAnyString(query, []string{"region", "region_id", "cloudregion", "cloudregion_id"}) + if len(regionStr) > 0 { + regionObj, err := CloudregionManager.FetchByIdOrName(nil, regionStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudregionManager.Keyword(), regionStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + cachedImages := CachedimageManager.Query().SubQuery() + storagecachedImages := StoragecachedimageManager.Query().SubQuery() + storageCaches := StoragecacheManager.Query().SubQuery() + storages := StorageManager.Query().SubQuery() + zones := ZoneManager.Query().SubQuery() + + subq := cachedImages.Query(cachedImages.Field("id")) + subq = subq.Join(storagecachedImages, sqlchemy.Equals(cachedImages.Field("id"), storagecachedImages.Field("cachedimage_id"))) + subq = subq.Join(storageCaches, sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), storageCaches.Field("id"))) + subq = subq.Join(storages, sqlchemy.Equals(storageCaches.Field("id"), storages.Field("storagecache_id"))) + subq = subq.Join(zones, sqlchemy.Equals(storages.Field("zone_id"), zones.Field("id"))) + subq = subq.Filter(sqlchemy.Equals(zones.Field("cloudregion_id"), regionObj.GetId())) + + q = q.Filter(sqlchemy.In(q.Field("id"), subq.SubQuery())) + } + + zoneStr := jsonutils.GetAnyString(query, []string{"zone", "zone_id"}) + if len(zoneStr) > 0 { + zoneObj, err := ZoneManager.FetchByIdOrName(nil, zoneStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(ZoneManager.Keyword(), zoneStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + cachedImages := CachedimageManager.Query().SubQuery() + storagecachedImages := StoragecachedimageManager.Query().SubQuery() + storageCaches := StoragecacheManager.Query().SubQuery() + storages := StorageManager.Query().SubQuery() + + subq := cachedImages.Query(cachedImages.Field("id")) + subq = subq.Join(storagecachedImages, sqlchemy.Equals(cachedImages.Field("id"), storagecachedImages.Field("cachedimage_id"))) + subq = subq.Join(storageCaches, sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), storageCaches.Field("id"))) + subq = subq.Join(storages, sqlchemy.Equals(storageCaches.Field("id"), storages.Field("storagecache_id"))) + subq = subq.Filter(sqlchemy.Equals(storages.Field("zone_id"), zoneObj.GetId())) + + q = q.Filter(sqlchemy.In(q.Field("id"), subq.SubQuery())) + } + + return q, nil +} diff --git a/pkg/compute/models/capabilities.go b/pkg/compute/models/capabilities.go index 352a36435c..c55ab7758f 100644 --- a/pkg/compute/models/capabilities.go +++ b/pkg/compute/models/capabilities.go @@ -14,6 +14,7 @@ type SCapabilities struct { Hypervisors []string `json:",allowempty"` ResourceTypes []string `json:",allowempty"` StorageTypes []string `json:",allowempty"` + DataStorageTypes []string `json:",allowempty"` GPUModels []string `json:",allowempty"` MinNicCount int MaxNicCount int @@ -28,7 +29,8 @@ func GetCapabilities(ctx context.Context, userCred mcclient.TokenCredential, que capa := SCapabilities{} capa.Hypervisors = getHypervisors(zone) capa.ResourceTypes = getResourceTypes(zone) - capa.StorageTypes = getStorageTypes(zone) + capa.StorageTypes = getStorageTypes(zone, true) + capa.DataStorageTypes = getStorageTypes(zone, false) capa.GPUModels = getGPUs(zone) capa.SchedPolicySupport = isSchedPolicySupported(zone) capa.MinNicCount = getMinNicCount(zone) @@ -97,7 +99,7 @@ func getResourceTypes(zone *SZone) []string { return resourceTypes } -func getStorageTypes(zone *SZone) []string { +func getStorageTypes(zone *SZone, isSysDisk bool) []string { storages := StorageManager.Query().SubQuery() hostStorages := HoststorageManager.Query().SubQuery() hosts := HostManager.Query().SubQuery() @@ -121,6 +123,9 @@ func getStorageTypes(zone *SZone) []string { q = q.Filter(sqlchemy.IsNotNull(storages.Field("medium_type"))) q = q.Filter(sqlchemy.In(storages.Field("status"), []string{STORAGE_ENABLED, STORAGE_ONLINE})) q = q.Filter(sqlchemy.IsTrue(storages.Field("enabled"))) + if isSysDisk { + q = q.Filter(sqlchemy.IsTrue(storages.Field("is_sys_disk_store"))) + } q = q.Distinct() rows, err := q.Rows() if err != nil { diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 071a077715..343b393ebe 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -142,6 +142,10 @@ func (manager *SCloudaccountManager) ValidateCreateData(ctx context.Context, use if !cloudprovider.IsSupported(provider) { return nil, httperrors.NewInputParameterError("Unsupported provider %s", provider) } + providerDriver, _ := cloudprovider.GetProviderDriver(provider) + if err := providerDriver.ValidateCreateCloudaccountData(ctx, userCred, data); err != nil { + return nil, err + } // check duplication // url, account, provider must be unique account, _ := data.GetString("account") @@ -165,8 +169,8 @@ func (manager *SCloudaccountManager) ValidateCreateData(ctx context.Context, use if err == cloudprovider.ErrNoSuchProvder { return nil, httperrors.NewResourceNotFoundError("no such provider %s", provider) } - log.Debugf("ValidateCreateData %s", err.Error()) - return nil, httperrors.NewInputParameterError("invalid cloud account info") + //log.Debugf("ValidateCreateData %s", err.Error()) + return nil, httperrors.NewInputParameterError("invalid cloud account info error: %s", err.Error()) } return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) @@ -245,47 +249,36 @@ func (self *SCloudaccount) PerformUpdateCredential(ctx context.Context, userCred return nil, httperrors.NewInvalidStatusError("Account disabled") } - var err error - changed := false - secret, _ := data.GetString("secret") - account, _ := data.GetString("account") - if len(account) > 0 && self.Provider == CLOUD_PROVIDER_AZURE { - return nil, httperrors.NewInputParameterError("not allow update azure tenant info") + providerDriver, _ := cloudprovider.GetProviderDriver(self.Provider) + account, err := providerDriver.ValidateUpdateCloudaccountCredential(ctx, userCred, data, self.Account) + if err != nil { + return nil, err } - accessUrl, _ := data.GetString("access_url") - if len(secret) > 0 || len(account) > 0 || len(accessUrl) > 0 { + + changed := false + if len(account.Secret) > 0 || len(account.Account) > 0 { // check duplication q := self.GetModelManager().Query() - q = q.Equals("access_url", accessUrl) - q = q.Equals("account", account) + q = q.Equals("account", account.Account) + q = q.Equals("access_url", self.AccessUrl) q = q.NotEquals("id", self.Id) if q.Count() > 0 { - return nil, httperrors.NewConflictError("Access url and account conflict") + return nil, httperrors.NewConflictError("account %s conflict", account.Account) } } - validateUrl := self.AccessUrl - if len(accessUrl) > 0 { - validateUrl = accessUrl - } - validateAccount := self.Account - if len(account) > 0 { - validateAccount = account - } - validateSecret, _ := self.getPassword() - if len(secret) > 0 { - validateSecret = secret - } - if err := cloudprovider.IsValidCloudAccount(validateUrl, validateAccount, validateSecret, self.Provider); err != nil { - return nil, httperrors.NewInputParameterError("invalid cloud account info") + originSecret, _ := self.getPassword() + + if err := cloudprovider.IsValidCloudAccount(self.AccessUrl, account.Account, account.Secret, self.Provider); err != nil { + return nil, httperrors.NewInputParameterError("invalid cloud account info error: %s", err.Error()) } - if (len(account) > 0 && account != self.Account) || (len(accessUrl) > 0 && accessUrl != self.AccessUrl) { - if len(account) > 0 && account != self.Account { + if (account.Account != self.Account) || (account.Secret != originSecret) { + if account.Account != self.Account { for _, cloudprovider := range self.GetCloudproviders() { if cloudprovider.Account == self.Account { _, err = cloudprovider.GetModelManager().TableSpec().Update(&cloudprovider, func() error { - cloudprovider.Account = account + cloudprovider.Account = account.Account return nil }) if err != nil { @@ -295,30 +288,21 @@ func (self *SCloudaccount) PerformUpdateCredential(ctx context.Context, userCred } } _, err = self.GetModelManager().TableSpec().Update(self, func() error { - if len(account) > 0 { - self.Account = account - } - if len(accessUrl) > 0 { - self.AccessUrl = accessUrl - } + self.Account = account.Account return nil }) if err != nil { return nil, err } - changed = true - } - if len(secret) > 0 { - err = self.savePassword(secret) + err = self.savePassword(account.Secret) if err != nil { return nil, err } for _, provider := range self.GetCloudproviders() { - provider.savePassword(secret) + provider.savePassword(account.Secret) } - changed = true } @@ -345,10 +329,13 @@ func (self *SCloudaccount) StartSyncCloudProviderInfoTask(ctx context.Context, u taskItems = append(taskItems, &cloudProviders[i]) } } + originState := self.Status + self.MarkStartSync(userCred) task, err := taskman.TaskManager.NewParallelTask(ctx, "CloudAccountSyncInfoTask", taskItems, userCred, params, "", "", nil) if err != nil { log.Errorf("CloudAccountSyncInfoTask newTask error %s", err) + self.SetStatus(userCred, originState, err.Error()) } else { task.ScheduleRun(nil) } @@ -829,3 +816,39 @@ func (self *SCloudaccount) PerformChangeProject(ctx context.Context, userCred mc } return providers[0].PerformChangeProject(ctx, userCred, query, data) } + +func (manager *SCloudaccountManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + accountStr, _ := query.GetString("account") + if len(accountStr) > 0 { + queryDict := query.(*jsonutils.JSONDict) + queryDict.Remove("account") + accountObj, err := manager.FetchByIdOrName(userCred, accountStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), accountStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + q = q.Equals("id", accountObj.GetId()) + } + + q, err := manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + managerStr, _ := query.GetString("manager") + if len(managerStr) > 0 { + providerObj, err := CloudproviderManager.FetchByIdOrName(userCred, managerStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudproviderManager.Keyword(), managerStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + provider := providerObj.(*SCloudprovider) + q = q.Equals("id", provider.CloudaccountId) + } + return q, nil +} diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index c5613bc4f8..5b402b9f28 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -45,6 +45,17 @@ const ( var ( CLOUD_PROVIDER_VALID_STATUS = []string{CLOUD_PROVIDER_CONNECTED, CLOUD_PROVIDER_START_SYNC, CLOUD_PROVIDER_SYNCING} + + CLOUD_PROVIDERS = []string{ + CLOUD_PROVIDER_KVM, + CLOUD_PROVIDER_VMWARE, + CLOUD_PROVIDER_ALIYUN, + CLOUD_PROVIDER_QCLOUD, + CLOUD_PROVIDER_AZURE, + CLOUD_PROVIDER_AWS, + CLOUD_PROVIDER_HUAWEI, + CLOUD_PROVIDER_OPENSTACK, + } ) type SCloudproviderManager struct { @@ -75,7 +86,7 @@ type SCloudprovider struct { Account string `width:"128" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(64, charset='ascii'), nullable=False) Secret string `width:"256" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(256, charset='ascii'), nullable=False) - CloudaccountId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` + CloudaccountId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` ProjectId string `name:"tenant_id" width:"128" charset:"ascii" nullable:"true" list:"admin"` @@ -122,6 +133,7 @@ func (self *SCloudprovider) ValidateDeleteCondition(ctx context.Context) error { } usage := self.getUsage() if !usage.isEmpty() { + log.Errorf("======Usage %#v", usage) return httperrors.NewNotEmptyError("Not an empty cloud provider") } return self.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) @@ -587,7 +599,7 @@ func (self *SCloudprovider) getMoreDetails(ctx context.Context, extra *jsonutils } account := self.GetCloudaccount() if account != nil { - extra.Add(jsonutils.NewString(account.GetName()), "account") + extra.Add(jsonutils.NewString(account.GetName()), "cloudaccount") } return extra } @@ -699,3 +711,37 @@ func (self *SCloudprovider) GetDetailsBalance(ctx context.Context, userCred mccl ret.Add(jsonutils.NewFloat(balance), "balance") return ret, nil } + +func (manager *SCloudproviderManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + accountStr, _ := query.GetString("account") + if len(accountStr) > 0 { + queryDict := query.(*jsonutils.JSONDict) + queryDict.Remove("account") + accountObj, err := CloudaccountManager.FetchByIdOrName(userCred, accountStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), accountStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + q = q.Equals("cloudaccount_id", accountObj.GetId()) + } + q, err := manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + managerStr, _ := query.GetString("manager") + if len(managerStr) > 0 { + providerObj, err := manager.FetchByIdOrName(userCred, managerStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudproviderManager.Keyword(), managerStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + q = q.Equals("id", providerObj.GetId()) + } + return q, nil +} diff --git a/pkg/compute/models/cloudregions.go b/pkg/compute/models/cloudregions.go index 12136fea33..b4df1481d3 100644 --- a/pkg/compute/models/cloudregions.go +++ b/pkg/compute/models/cloudregions.go @@ -10,6 +10,7 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -127,6 +128,17 @@ func (self *SCloudregion) GetVpcCount() int { } } +func (self *SCloudregion) GetDriver() IRegionDriver { + provider := self.Provider + if len(provider) == 0 { + provider = CLOUD_PROVIDER_KVM + } + if !utils.IsInStringArray(provider, CLOUD_PROVIDERS) { + log.Fatalf("Unsupported region provider %s", provider) + } + return GetRegionDriver(provider) +} + func (self *SCloudregion) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { extra.Add(jsonutils.NewInt(int64(self.GetVpcCount())), "vpc_count") extra.Add(jsonutils.NewInt(int64(self.GetZoneCount())), "zone_count") @@ -347,12 +359,30 @@ func (manager *SCloudregionManager) ListItemFilter(ctx context.Context, q *sqlch } managerStr, _ := query.GetString("manager") if len(managerStr) > 0 { - manager := CloudproviderManager.FetchCloudproviderByIdOrName(managerStr) - if manager == nil { - return nil, httperrors.NewResourceNotFoundError("Cloud provider/manager %s not found", managerStr) + managerObj, err := CloudproviderManager.FetchByIdOrName(userCred, managerStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudproviderManager.Keyword(), managerStr) + } else { + return nil, httperrors.NewGeneralError(err) + } } + manager := managerObj.(*SCloudprovider) q = q.Equals("provider", manager.Provider) } + accountStr, _ := query.GetString("account") + if len(accountStr) > 0 { + accountObj, err := CloudaccountManager.FetchByIdOrName(userCred, accountStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudaccountManager.Keyword(), accountStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + account := accountObj.(*SCloudaccount) + q = q.In("provider", account.Provider) + } if jsonutils.QueryBoolean(query, "usable", false) || jsonutils.QueryBoolean(query, "usable_vpc", false) { providers := CloudproviderManager.Query().SubQuery() diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 396096eae8..4ebb561e42 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -167,6 +167,15 @@ func (manager *SDiskManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQu sq := storages.Query(storages.Field("id")).Filter(sqlchemy.NotIn(storages.Field("storage_type"), STORAGE_LOCAL_TYPES)) q = q.Filter(sqlchemy.In(q.Field("storage_id"), sq)) } + + if jsonutils.QueryBoolean(query, "public_cloud", false) { + sq := storages.Query(storages.Field("id")).Filter(sqlchemy.IsNotNull(storages.Field("manager_id"))) + q = q.Filter(sqlchemy.In(q.Field("storage_id"), sq)) + } else if jsonutils.QueryBoolean(query, "private_cloud", false) { + sq := storages.Query(storages.Field("id")).Filter(sqlchemy.IsNull(storages.Field("manager_id"))) + q = q.Filter(sqlchemy.In(q.Field("storage_id"), sq)) + } + if jsonutils.QueryBoolean(query, "local", false) { sq := storages.Query(storages.Field("id")).Filter(sqlchemy.In(storages.Field("storage_type"), STORAGE_LOCAL_TYPES)) q = q.Filter(sqlchemy.In(q.Field("storage_id"), sq)) @@ -676,7 +685,7 @@ func (self *SDisk) PerformResize(ctx context.Context, userCred mcclient.TokenCre if err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), &pendingUsage); err != nil { return nil, httperrors.NewOutOfQuotaError(err.Error()) } - return nil, self.StartDiskResizeTask(ctx, userCred, int64(sizeMb), "", &pendingUsage) + return nil, self.StartDiskResizeTask(ctx, userCred, int64(sizeMb), "", &pendingUsage, nil) } func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { @@ -864,7 +873,7 @@ func (manager *SDiskManager) getDisksByStorage(storage *SStorage) ([]SDisk, erro return disks, nil } -func (manager *SDiskManager) syncCloudDisk(ctx context.Context, userCred mcclient.TokenCredential, vdisk cloudprovider.ICloudDisk, index int, projectId string, projectSync bool) (*SDisk, error) { +func (manager *SDiskManager) syncCloudDisk(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, vdisk cloudprovider.ICloudDisk, index int, projectId string, projectSync bool) (*SDisk, error) { diskObj, err := manager.FetchByExternalId(vdisk.GetGlobalId()) if err != nil { if err == sql.ErrNoRows { @@ -876,13 +885,13 @@ func (manager *SDiskManager) syncCloudDisk(ctx context.Context, userCred mcclien return nil, err } storage := storageObj.(*SStorage) - return manager.newFromCloudDisk(ctx, userCred, vdisk, storage, -1, projectId) + return manager.newFromCloudDisk(ctx, userCred, provider, vdisk, storage, -1, projectId) } else { return nil, err } } else { disk := diskObj.(*SDisk) - err = disk.syncWithCloudDisk(ctx, userCred, vdisk, index, projectId, projectSync) + err = disk.syncWithCloudDisk(ctx, userCred, provider, vdisk, index, projectId, projectSync) if err != nil { return nil, err } @@ -890,7 +899,7 @@ func (manager *SDiskManager) syncCloudDisk(ctx context.Context, userCred mcclien } } -func (manager *SDiskManager) SyncDisks(ctx context.Context, userCred mcclient.TokenCredential, storage *SStorage, disks []cloudprovider.ICloudDisk, projectId string, projectSync bool) ([]SDisk, []cloudprovider.ICloudDisk, compare.SyncResult) { +func (manager *SDiskManager) SyncDisks(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, storage *SStorage, disks []cloudprovider.ICloudDisk, projectId string, projectSync bool) ([]SDisk, []cloudprovider.ICloudDisk, compare.SyncResult) { localDisks := make([]SDisk, 0) remoteDisks := make([]cloudprovider.ICloudDisk, 0) syncResult := compare.SyncResult{} @@ -922,7 +931,7 @@ func (manager *SDiskManager) SyncDisks(ctx context.Context, userCred mcclient.To } for i := 0; i < len(commondb); i += 1 { - err = commondb[i].syncWithCloudDisk(ctx, userCred, commonext[i], -1, projectId, projectSync) + err = commondb[i].syncWithCloudDisk(ctx, userCred, provider, commonext[i], -1, projectId, projectSync) if err != nil { syncResult.UpdateError(err) } else { @@ -933,7 +942,7 @@ func (manager *SDiskManager) SyncDisks(ctx context.Context, userCred mcclient.To } for i := 0; i < len(added); i += 1 { - new, err := manager.newFromCloudDisk(ctx, userCred, added[i], storage, -1, projectId) + new, err := manager.newFromCloudDisk(ctx, userCred, provider, added[i], storage, -1, projectId) if err != nil { syncResult.AddError(err) } else { @@ -946,10 +955,10 @@ func (manager *SDiskManager) SyncDisks(ctx context.Context, userCred mcclient.To return localDisks, remoteDisks, syncResult } -func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.TokenCredential, extDisk cloudprovider.ICloudDisk, index int, projectId string, projectSync bool) error { +func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, extDisk cloudprovider.ICloudDisk, index int, projectId string, projectSync bool) error { recycle := false guests := self.GetGuests() - if len(guests) == 1 && guests[0].IsPrepaidRecycle() { + if provider.SupportPrepaidResources() && len(guests) == 1 && guests[0].IsPrepaidRecycle() { recycle = true } _, err := self.GetModelManager().TableSpec().Update(self, func() error { @@ -972,7 +981,7 @@ func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.Toke self.IsEmulated = extDisk.IsEmulated() - if !recycle { + if provider.SupportPrepaidResources() && !recycle { self.BillingType = extDisk.GetBillingType() self.ExpiredAt = extDisk.GetExpiredAt() } @@ -1004,7 +1013,7 @@ func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.Toke return nil } -func (manager *SDiskManager) newFromCloudDisk(ctx context.Context, userCred mcclient.TokenCredential, extDisk cloudprovider.ICloudDisk, storage *SStorage, index int, projectId string) (*SDisk, error) { +func (manager *SDiskManager) newFromCloudDisk(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, extDisk cloudprovider.ICloudDisk, storage *SStorage, index int, projectId string) (*SDisk, error) { disk := SDisk{} disk.SetModelManager(manager) @@ -1027,8 +1036,10 @@ func (manager *SDiskManager) newFromCloudDisk(ctx context.Context, userCred mccl disk.IsEmulated = extDisk.IsEmulated() - disk.BillingType = extDisk.GetBillingType() - disk.ExpiredAt = extDisk.GetExpiredAt() + if provider.SupportPrepaidResources() { + disk.BillingType = extDisk.GetBillingType() + disk.ExpiredAt = extDisk.GetExpiredAt() + } err := manager.TableSpec().Insert(&disk) if err != nil { @@ -1090,23 +1101,23 @@ func totalDiskSize(projectId string, active tristate.TriState, ready tristate.Tr } type SDiskConfig struct { - ImageId string + ImageId string `json:"image_id"` - SnapshotId string - DiskType string // sys, data, swap, volume + SnapshotId string `json:"snapshot_id"` + DiskType string `json:"disk_type"` // sys, data, swap, volume // ImageDiskFormat string - SizeMb int // MB - Fs string // file system - Format string // - Driver string // - Cache string // - Mountpoint string // - Backend string // stroageType - Medium string - ImageProperties map[string]string + SizeMb int `json:"size"` // MB + Fs string `json:"fs"` // file system + Format string `json:"format"` // + Driver string `json:"driver"` // + Cache string `json:"cache"` // + Mountpoint string `json:"mountpoint"` // + Backend string `json:"backend"` // stroageType + Medium string `json:"medium"` + ImageProperties map[string]string `json:"image_properties"` - DiskId string // import only + DiskId string `json:"-"` // import only } func parseDiskInfo(ctx context.Context, userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SDiskConfig, error) { @@ -1167,9 +1178,11 @@ func parseDiskInfo(ctx context.Context, userCred mcclient.TokenCredential, info } } } - if len(diskConfig.ImageId) > 0 && diskConfig.SizeMb == 0 { - diskConfig.SizeMb = options.Options.DefaultDiskSize // MB - } else if len(diskConfig.ImageId) == 0 && diskConfig.SizeMb == 0 { + // XXX: do not set default disk size here, set it by each hypervisor driver + // if len(diskConfig.ImageId) > 0 && diskConfig.SizeMb == 0 { + // diskConfig.SizeMb = options.Options.DefaultDiskSize // MB + // else + if len(diskConfig.ImageId) == 0 && diskConfig.SizeMb == 0 { return nil, httperrors.NewInputParameterError("Diskinfo not contains either imageID or size") } return &diskConfig, nil @@ -1212,7 +1225,7 @@ func fillDiskConfigByImage(ctx context.Context, userCred mcclient.TokenCredentia log.Errorf("getImageInfo fail %s", err) return err } - if image.Status != IMAGE_STATUS_ACTIVE { + if image.Status != cloudprovider.IMAGE_STATUS_ACTIVE { return httperrors.NewInvalidStatusError("Image status is not active") } diskConfig.ImageId = image.Id @@ -1229,13 +1242,13 @@ func fillDiskConfigByImage(ctx context.Context, userCred mcclient.TokenCredentia return nil } -func parseIsoInfo(ctx context.Context, userCred mcclient.TokenCredential, imageId string) (*SImage, error) { +func parseIsoInfo(ctx context.Context, userCred mcclient.TokenCredential, imageId string) (*cloudprovider.SImage, error) { image, err := CachedimageManager.getImageInfo(ctx, userCred, imageId, false) if err != nil { log.Errorf("getImageInfo fail %s", err) return nil, err } - if image.Status != IMAGE_STATUS_ACTIVE { + if image.Status != cloudprovider.IMAGE_STATUS_ACTIVE { return nil, httperrors.NewInvalidStatusError("Image status is not active") } return image, nil @@ -1390,9 +1403,12 @@ func (self *SDisk) GetCustomizeColumns(ctx context.Context, userCred mcclient.To return self.getMoreDetails(extra) } -func (self *SDisk) StartDiskResizeTask(ctx context.Context, userCred mcclient.TokenCredential, sizeMb int64, parentTaskId string, pendingUsage quotas.IQuota) error { +func (self *SDisk) StartDiskResizeTask(ctx context.Context, userCred mcclient.TokenCredential, sizeMb int64, parentTaskId string, pendingUsage quotas.IQuota, guest *SGuest) error { params := jsonutils.NewDict() params.Add(jsonutils.NewInt(sizeMb), "size") + if guest != nil { + params.Add(jsonutils.NewString(guest.Id), "guest_id") + } if task, err := taskman.TaskManager.NewTask(ctx, "DiskResizeTask", self, userCred, params, parentTaskId, "", pendingUsage); err != nil { return err } else { diff --git a/pkg/compute/models/elasticips.go b/pkg/compute/models/elasticips.go index 183b96b240..a49670678b 100644 --- a/pkg/compute/models/elasticips.go +++ b/pkg/compute/models/elasticips.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/tristate" @@ -76,8 +77,9 @@ type SElasticip struct { Bandwidth int `list:"user" create:"required"` ChargeType string `list:"user" create:"required"` + BgpType string `list:"user" create:"optional"` // 目前只有华为云此字段是必需填写的。 - AutoDellocate tristate.TriState `default:"false" get:"user" create:"optional"` + AutoDellocate tristate.TriState `default:"false" get:"user" create:"optional" update:"user"` CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` } @@ -351,6 +353,16 @@ func (manager *SElasticipManager) getEipForInstance(instanceType string, instanc return &eip, nil } +func (self *SElasticip) IsAssociated() bool { + if len(self.AssociateId) == 0 { + return false + } + if self.GetAssociateVM() != nil { + return true + } + return false +} + func (self *SElasticip) GetAssociateVM() *SGuest { if self.AssociateType == "server" && len(self.AssociateId) > 0 { return GuestManager.FetchGuestById(self.AssociateId) @@ -386,6 +398,9 @@ func (self *SElasticip) Dissociate(ctx context.Context, userCred mcclient.TokenC } func (self *SElasticip) AssociateVM(ctx context.Context, userCred mcclient.TokenCredential, vm *SGuest) error { + if vm.PendingDeleted || vm.Deleted { + return fmt.Errorf("vm is deleted") + } if len(self.AssociateType) > 0 { return fmt.Errorf("EIP has been associated!!") } @@ -505,7 +520,7 @@ func (self *SElasticip) CustomizeDelete(ctx context.Context, userCred mcclient.T } func (self *SElasticip) ValidateDeleteCondition(ctx context.Context) error { - if len(self.AssociateId) > 0 { + if self.IsAssociated() { return fmt.Errorf("eip is associated with instance") } return self.SVirtualResourceBase.ValidateDeleteCondition(ctx) @@ -527,7 +542,7 @@ func (self *SElasticip) AllowPerformAssociate(ctx context.Context, userCred mccl } func (self *SElasticip) PerformAssociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if len(self.AssociateId) > 0 { + if self.IsAssociated() { return nil, httperrors.NewConflictError("eip has been associated with instance") } @@ -602,17 +617,21 @@ func (self *SElasticip) PerformAssociate(ctx context.Context, userCred mcclient. return nil, httperrors.NewInputParameterError("server and eip are not managed by the same provider") } + err = self.StartEipAssociateInstanceTask(ctx, userCred, server, "") + return nil, err +} + +func (self *SElasticip) StartEipAssociateInstanceTask(ctx context.Context, userCred mcclient.TokenCredential, server *SGuest, parentTaskId string) error { params := jsonutils.NewDict() params.Add(jsonutils.NewString(server.ExternalId), "instance_external_id") params.Add(jsonutils.NewString(server.Id), "instance_id") params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") - err = self.StartEipAssociateTask(ctx, userCred, params) - return nil, err + return self.StartEipAssociateTask(ctx, userCred, params, parentTaskId) } -func (self *SElasticip) StartEipAssociateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict) error { - task, err := taskman.TaskManager.NewTask(ctx, "EipAssociateTask", self, userCred, params, "", "", nil) +func (self *SElasticip) StartEipAssociateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "EipAssociateTask", self, userCred, params, parentTaskId, "", nil) if err != nil { log.Errorf("create EipAssociateTask task fail %s", err) return err @@ -628,7 +647,12 @@ func (self *SElasticip) AllowPerformDissociate(ctx context.Context, userCred mcc func (self *SElasticip) PerformDissociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { if len(self.AssociateId) == 0 { - return nil, httperrors.NewConflictError("eip is not associated with instance") + return nil, nil // success + } + + // associate with an invalid vm + if !self.IsAssociated() { + return nil, self.Dissociate(ctx, userCred) } if self.Status != EIP_STATUS_READY { @@ -639,12 +663,18 @@ func (self *SElasticip) PerformDissociate(ctx context.Context, userCred mcclient return nil, httperrors.NewUnsupportOperationError("fixed public eip cannot be dissociated") } - err := self.StartEipDissociateTask(ctx, userCred, "") + autoDelete := jsonutils.QueryBoolean(data, "auto_delete", false) + + err := self.StartEipDissociateTask(ctx, userCred, autoDelete, "") return nil, err } -func (self *SElasticip) StartEipDissociateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { - task, err := taskman.TaskManager.NewTask(ctx, "EipDissociateTask", self, userCred, nil, parentTaskId, "", nil) +func (self *SElasticip) StartEipDissociateTask(ctx context.Context, userCred mcclient.TokenCredential, autoDelete bool, parentTaskId string) error { + params := jsonutils.NewDict() + if autoDelete { + params.Add(jsonutils.JSONTrue, "auto_delete") + } + task, err := taskman.TaskManager.NewTask(ctx, "EipDissociateTask", self, userCred, params, parentTaskId, "", nil) if err != nil { log.Errorf("create EipDissociateTask fail %s", err) return nil @@ -718,41 +748,38 @@ func (self *SElasticip) GetCustomizeColumns(ctx context.Context, userCred mcclie } func (self *SElasticip) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { - if cloudprovider := self.GetCloudprovider(); cloudprovider != nil { - extra.Add(jsonutils.NewString(cloudprovider.Provider), "provider") - } + info := self.getCloudProviderInfo() + extra.Update(jsonutils.Marshal(&info)) vm := self.GetAssociateVM() if vm != nil { extra.Add(jsonutils.NewString(vm.GetName()), "associate_name") } - region := self.GetRegion() - if region != nil { - extra.Add(jsonutils.NewString(region.GetName()), "cloudregion") - extra.Add(jsonutils.NewString(region.GetName()), "region") - } return extra } -func (manager *SElasticipManager) allocateEipAndAssociateVM(ctx context.Context, userCred mcclient.TokenCredential, vm *SGuest, bw int, chargeType string, managerId string, regionId string) error { - eipPendingUsage := &SQuota{Eip: 1} - err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), eipPendingUsage) - if err != nil { - return httperrors.NewOutOfQuotaError("Out of eip quota: %s", err) +func (manager *SElasticipManager) AllocateEipAndAssociateVM(ctx context.Context, userCred mcclient.TokenCredential, vm *SGuest, bw int, chargeType string, eipPendingUsage quotas.IQuota) error { + + host := vm.GetHost() + region := host.GetRegion() + + if len(chargeType) == 0 { + chargeType = EIP_CHARGE_TYPE_BY_TRAFFIC } eip := SElasticip{} eip.SetModelManager(manager) eip.Mode = EIP_MODE_STANDALONE_EIP - eip.AutoDellocate = tristate.True + // do not implicitly auto dellocate EIP, should be set by user explicitly + // eip.AutoDellocate = tristate.True eip.Bandwidth = bw eip.ChargeType = chargeType eip.ProjectId = vm.ProjectId - eip.ManagerId = managerId - eip.CloudregionId = regionId + eip.ManagerId = host.ManagerId + eip.CloudregionId = region.Id eip.Name = fmt.Sprintf("eip-for-%s", vm.GetName()) - err = manager.TableSpec().Insert(&eip) + err := manager.TableSpec().Insert(&eip) if err != nil { log.Errorf("create EIP record fail %s", err) return err @@ -763,6 +790,8 @@ func (manager *SElasticipManager) allocateEipAndAssociateVM(ctx context.Context, params.Add(jsonutils.NewString(vm.Id), "instance_id") params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") + vm.SetStatus(userCred, VM_ASSOCIATE_EIP, "allocate and associate EIP") + return eip.startEipAllocateTask(ctx, userCred, params, eipPendingUsage) } diff --git a/pkg/compute/models/groupguests.go b/pkg/compute/models/groupguests.go index d85a97e2d7..1160d81613 100644 --- a/pkg/compute/models/groupguests.go +++ b/pkg/compute/models/groupguests.go @@ -32,8 +32,8 @@ func init() { type SGroupguest struct { SGroupJointsBase - Tag string `width:"256" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(256, charset='ascii'), nullable=True) - GuestId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + Tag string `width:"256" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(256, charset='ascii'), nullable=True) + GuestId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) } func (joint *SGroupguest) Master() db.IStandaloneModel { diff --git a/pkg/compute/models/groupjoints.go b/pkg/compute/models/groupjoints.go index ae583de1bf..3daa0351b4 100644 --- a/pkg/compute/models/groupjoints.go +++ b/pkg/compute/models/groupjoints.go @@ -22,10 +22,10 @@ func NewGroupJointsManager(dt interface{}, tableName string, keyword string, key type SGroupJointsBase struct { db.SVirtualJointResourceBase - SrvtagId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + SrvtagId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) } -func (self *SGroupJointsBase) GetGroup() *SGuest { +func (self *SGroupJointsBase) GetGroup() *SGroup { guest, _ := GroupManager.FetchById(self.SrvtagId) - return guest.(*SGuest) + return guest.(*SGroup) } diff --git a/pkg/compute/models/groupnetworks.go b/pkg/compute/models/groupnetworks.go index a934d2785e..ff496a7390 100644 --- a/pkg/compute/models/groupnetworks.go +++ b/pkg/compute/models/groupnetworks.go @@ -25,7 +25,7 @@ func init() { type SGroupnetwork struct { SGroupJointsBase - NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) IpAddr string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True) // # ip6_addr = Column(VARCHAR(64, charset='ascii'), nullable=True) @@ -56,7 +56,7 @@ func (self *SGroupnetwork) GetExtraDetails(ctx context.Context, userCred mcclien return db.JointModelExtra(self, extra), nil } -func (self *SGroupnetwork) getNetwork() *SNetwork { +func (self *SGroupnetwork) GetNetwork() *SNetwork { obj, err := NetworkManager.FetchById(self.NetworkId) if err != nil { log.Errorf("%s", err) diff --git a/pkg/compute/models/groups.go b/pkg/compute/models/groups.go index ef23b236a4..f3f0bc77c4 100644 --- a/pkg/compute/models/groups.go +++ b/pkg/compute/models/groups.go @@ -35,3 +35,13 @@ type SGroup struct { SchedStrategy string `width:"16" charset:"ascii" nullable:"true" default:"" list:"user" update:"user" create:"optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True, default='') } + +func (group *SGroup) GetNetworks() ([]SGroupnetwork, error) { + q := GroupnetworkManager.Query().Equals("group_id", group.Id) + groupnets := make([]SGroupnetwork, 0) + err := db.FetchModelObjects(GroupnetworkManager, q, &groupnets) + if err != nil { + return nil, err + } + return groupnets, nil +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 111b77d2a7..d0e60cc021 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -28,6 +28,7 @@ import ( "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/httputils" "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/onecloud/pkg/util/seclib2" @@ -82,7 +83,7 @@ func (self *SGuest) GetDetailsDesc(ctx context.Context, userCred mcclient.TokenC if host == nil { return nil, httperrors.NewInvalidStatusError("No host for server") } - desc := self.GetDriver().GetJsonDescAtHost(ctx, self, host) + desc := self.GetDriver().GetJsonDescAtHost(ctx, userCred, self, host) return desc, nil } @@ -508,15 +509,15 @@ func (self *SGuest) StartGuestDeployTask(ctx context.Context, userCred mcclient. return nil } -func (self *SGuest) NotifyServerEvent(event string, priority string, loginInfo bool) error { +func (self *SGuest) NotifyServerEvent(event string, priority notify.TNotifyPriority, loginInfo bool) { meta, err := self.GetAllMetadata(nil) if err != nil { - return err + return } kwargs := jsonutils.NewDict() kwargs.Add(jsonutils.NewString(self.Name), "name") if loginInfo { - kwargs.Add(jsonutils.NewStringArray(self.getNotifyIps()), "ips") + kwargs.Add(jsonutils.NewString(self.getNotifyIps()), "ips") osName := meta["os_name"] if osName == "Windows" { kwargs.Add(jsonutils.JSONTrue, "windows") @@ -538,10 +539,10 @@ func (self *SGuest) NotifyServerEvent(event string, priority string, loginInfo b } } } - return notifyclient.Notify(self.ProjectId, event, priority, kwargs) + notifyclient.Notify(self.ProjectId, true, priority, event, kwargs) } -func (self *SGuest) NotifyAdminServerEvent(ctx context.Context, event string, priority string) error { +func (self *SGuest) NotifyAdminServerEvent(ctx context.Context, event string, priority notify.TNotifyPriority) { kwargs := jsonutils.NewDict() kwargs.Add(jsonutils.NewString(self.Name), "name") tc, _ := self.GetTenantCache(ctx) @@ -550,7 +551,7 @@ func (self *SGuest) NotifyAdminServerEvent(ctx context.Context, event string, pr } else { kwargs.Add(jsonutils.NewString(self.ProjectId), "tenant") } - return notifyclient.Notify(options.Options.NotifyAdminUser, event, priority, kwargs) + notifyclient.Notify(options.Options.NotifyAdminUser, true, priority, event, kwargs) } func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { @@ -1060,6 +1061,7 @@ func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient. } else { data.Set("all_disks", jsonutils.JSONFalse) } + self.SetStatus(userCred, VM_REBUILD_ROOT, "request start rebuild root") if self.GetHypervisor() == HYPERVISOR_BAREMETAL { task, err := taskman.TaskManager.NewTask(ctx, "BaremetalServerRebuildRootTask", self, userCred, data, "", "", nil) if err != nil { @@ -1260,7 +1262,7 @@ func (self *SGuest) detachIsolateDevice(ctx context.Context, userCred mcclient.T logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) return httperrors.NewBadRequestError(msg) } - _, err := self.GetModelManager().TableSpec().Update(dev, func() error { + _, err := dev.GetModelManager().TableSpec().Update(dev, func() error { dev.GuestId = "" return nil }) @@ -1309,6 +1311,115 @@ func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mc return nil, err } +func (self *SGuest) AllowPerformChangeIpaddr(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "change-ipaddr") +} + +func (self *SGuest) findGuestnetworkByInfo(ipStr string, macStr string, index int64) (*SGuestnetwork, error) { + if len(ipStr) > 0 { + gn, err := self.GetNetworkByIp(ipStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("ip %s not found", ipStr) + } + return nil, httperrors.NewGeneralError(err) + } + return gn, nil + } else if len(macStr) > 0 { + gn, err := self.GetNetworkByMac(macStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("mac %s not found", macStr) + } + return nil, httperrors.NewGeneralError(err) + } + return gn, nil + } else { + gns, err := self.GetNetworks("") + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if index >= 0 && index < int64(len(gns)) { + return &gns[index], nil + } + return nil, httperrors.NewInputParameterError("no either ip_addr or mac specified") + } +} + +func (self *SGuest) PerformChangeIpaddr(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != VM_READY && self.Status != VM_RUNNING { + return nil, httperrors.NewInvalidStatusError("Cannot change network ip_addr in status %s", self.Status) + } + + reserve := jsonutils.QueryBoolean(data, "reserve", false) + + ipStr, _ := data.GetString("ip_addr") + macStr, _ := data.GetString("mac") + index, _ := data.Int("index") + + gn, err := self.findGuestnetworkByInfo(ipStr, macStr, index) + if err != nil { + return nil, err + } + + netDesc, err := data.Get("net_desc") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + conf, err := parseNetworkInfo(userCred, netDesc) + if err != nil { + return nil, err + } + err = isValidNetworkInfo(userCred, conf) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + host := self.GetHost() + + _, err = func() (jsonutils.JSONObject, error) { + lockman.LockRawObject(ctx, GuestnetworkManager.KeywordPlural(), "") + defer lockman.ReleaseRawObject(ctx, GuestnetworkManager.KeywordPlural(), "") + + if len(conf.Mac) > 0 { + if conf.Mac != gn.MacAddr { + if self.Status != VM_READY { + // change mac + return nil, httperrors.NewInvalidStatusError("cannot change mac when guest is running") + } + // check mac duplication + if GuestnetworkManager.Query().Equals("mac_addr", conf.Mac).Count() > 0 { + return nil, httperrors.NewConflictError("mac addr %s has been occupied", conf.Mac) + } + } else { + if conf.Address == gn.IpAddr { // ip addr is the same, noop + return nil, nil + } + } + } else { + conf.Mac = gn.MacAddr + } + + err = self.detachNetworks(ctx, userCred, []SGuestnetwork{*gn}, reserve, false) + if err != nil { + return nil, err + } + conf.Ifname = gn.Ifname + err = self.attach2NetworkDesc(ctx, userCred, host, conf, nil) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + + return nil, nil + }() + + if err != nil { + return nil, err + } + + err = self.StartSyncTask(ctx, userCred, true, "") + return nil, err +} + func (self *SGuest) AllowPerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "detachnetwork") } @@ -1318,17 +1429,48 @@ func (self *SGuest) PerformDetachnetwork(ctx context.Context, userCred mcclient. return nil, httperrors.NewInvalidStatusError("Cannot detach network in status %s", self.Status) } reserve := jsonutils.QueryBoolean(data, "reserve", false) - netId, err := data.GetString("net_id") - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) + + netStr, _ := data.GetString("net_id") + if len(netStr) > 0 { + netObj, err := NetworkManager.FetchById(netStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(NetworkManager.Keyword(), netStr) + } + return nil, httperrors.NewGeneralError(err) + } + gns, err := self.GetNetworks(netObj.GetId()) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + err = self.detachNetworks(ctx, userCred, gns, reserve, true) + return nil, err } - iNetwork, err := NetworkManager.FetchById(netId) - if err != nil { - return nil, httperrors.NewNotFoundError("Network %s not found", netId) + ipStr, _ := data.GetString("ip_addr") + if len(ipStr) > 0 { + gn, err := self.GetNetworkByIp(ipStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("ip %s not found", ipStr) + } + return nil, httperrors.NewGeneralError(err) + } + err = self.detachNetworks(ctx, userCred, []SGuestnetwork{*gn}, reserve, true) + return nil, err } - network := iNetwork.(*SNetwork) - err = self.detachNetwork(ctx, userCred, network, reserve, true) - return nil, err + macStr, _ := data.GetString("mac") + if len(macStr) > 0 { + gn, err := self.GetNetworkByMac(macStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("mac %s not found", macStr) + } + return nil, httperrors.NewGeneralError(err) + } + err = self.detachNetworks(ctx, userCred, []SGuestnetwork{*gn}, reserve, true) + return nil, err + } + return nil, httperrors.NewInputParameterError("no either ip_addr, mac or network specified") } func (self *SGuest) AllowPerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -1387,33 +1529,34 @@ func (self *SGuest) AllowPerformChangeBandwidth(ctx context.Context, userCred mc } func (self *SGuest) PerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { - guestnics := self.GetNetworks() - index, err := data.Int("index") - if err != nil || index > int64(len(guestnics)) { - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, "Index Not fount or out of NIC index", userCred, false) - return nil, httperrors.NewBadRequestError("Index Not fount or out of NIC index") - } - bandwidth, err := data.Int("bandwidth") - if err != nil || bandwidth < 0 { - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, "Bandwidth must non-negative", userCred, false) - return nil, httperrors.NewBadRequestError("Bandwidth must be non-negative") - } - guestnic := &guestnics[index] - if guestnic.BwLimit != int(bandwidth) { - GuestnetworkManager.TableSpec().Update(guestnic, func() error { - guestnic.BwLimit = int(bandwidth) - return nil - }) - err := self.StartSyncTask(ctx, userCred, false, "") - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, err, userCred, err == nil) - return nil, err - } - return nil, nil + if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { + msg := fmt.Sprintf("Cannot change bandwidth in status %s", self.Status) + return nil, httperrors.NewBadRequestError(msg) } - msg := fmt.Sprintf("Cannot change bandwidth in status %s", self.Status) - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, msg, userCred, false) - return nil, httperrors.NewBadRequestError(msg) + + bandwidth, err := data.Int("bandwidth") + if err != nil || bandwidth < 0 { + return nil, httperrors.NewBadRequestError("Bandwidth must be non-negative") + } + + ipStr, _ := data.GetString("ip_addr") + macStr, _ := data.GetString("mac") + index, _ := data.Int("index") + guestnic, err := self.findGuestnetworkByInfo(ipStr, macStr, index) + if err != nil { + return nil, err + } + + if guestnic.BwLimit != int(bandwidth) { + GuestnetworkManager.TableSpec().Update(guestnic, func() error { + guestnic.BwLimit = int(bandwidth) + return nil + }) + err := self.StartSyncTask(ctx, userCred, false, "") + logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, err, userCred, err == nil) + return nil, err + } + return nil, nil } func (self *SGuest) AllowPerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -1421,6 +1564,10 @@ func (self *SGuest) AllowPerformChangeConfig(ctx context.Context, userCred mccli } func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !self.GetDriver().AllowReconfigGuest() { + return nil, httperrors.NewInvalidStatusError("Not allow to change config") + } + changeStatus, err := self.GetDriver().GetChangeConfigStatus() if err != nil { return nil, httperrors.NewInputParameterError(err.Error()) @@ -1428,15 +1575,15 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if !utils.IsInStringArray(self.Status, changeStatus) { return nil, httperrors.NewInvalidStatusError("Cannot change config in %s", self.Status) } - if !self.GetDriver().AllowReconfigGuest() { - return nil, httperrors.NewInvalidStatusError("Not allow to change config") - } + host := self.GetHost() if host == nil { return nil, httperrors.NewInvalidStatusError("No valid host") } var addCpu, addMem int + var cpuChanged, memChanged bool + confs := jsonutils.NewDict() skuId := jsonutils.GetAnyString(data, []string{"instance_type", "sku", "flavor"}) if len(skuId) > 0 { @@ -1444,11 +1591,22 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if err != nil { return nil, err } - addCpu = sku.CpuCoreCount - int(self.VcpuCount) - addMem = sku.MemorySizeMB - self.VmemSize - confs.Add(jsonutils.NewString(sku.ExternalId), "sku_id") - confs.Add(jsonutils.NewInt(int64(sku.CpuCoreCount)), "vcpu_count") - confs.Add(jsonutils.NewInt(int64(sku.MemorySizeMB)), "vmem_size") + + if sku.GetName() != self.InstanceType { + confs.Add(jsonutils.NewString(sku.GetName()), "instance_type") + confs.Add(jsonutils.NewInt(int64(sku.CpuCoreCount)), "vcpu_count") + confs.Add(jsonutils.NewInt(int64(sku.MemorySizeMB)), "vmem_size") + + if sku.CpuCoreCount != int(self.VcpuCount) { + cpuChanged = true + addCpu = sku.CpuCoreCount - int(self.VcpuCount) + } + if sku.MemorySizeMB != self.VmemSize { + memChanged = true + addMem = sku.MemorySizeMB - self.VmemSize + } + } + } else { vcpuCount, err := data.GetString("vcpu_count") if err == nil { @@ -1456,13 +1614,14 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if err != nil { return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") } - err = confs.Add(jsonutils.NewInt(nVcpu), "vcpu_count") - if err != nil { - return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") - } - addCpu = int(nVcpu - int64(self.VcpuCount)) - if addCpu < 0 { - addCpu = 0 + + if nVcpu != int64(self.VcpuCount) { + cpuChanged = true + addCpu = int(nVcpu - int64(self.VcpuCount)) + err = confs.Add(jsonutils.NewInt(nVcpu), "vcpu_count") + if err != nil { + return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") + } } } vmemSize, err := data.GetString("vmem_size") @@ -1474,17 +1633,28 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if err != nil { httperrors.NewBadRequestError("Params vmem_size parse error") } - err = confs.Add(jsonutils.NewInt(int64(nVmem)), "vmem_size") - if err != nil { - return nil, httperrors.NewBadRequestError("Params vmem_size parse error") - } - addMem = nVmem - self.VmemSize - if addMem < 0 { - addMem = 0 + if nVmem != self.VmemSize { + memChanged = true + addMem = nVmem - self.VmemSize + err = confs.Add(jsonutils.NewInt(int64(nVmem)), "vmem_size") + if err != nil { + return nil, httperrors.NewBadRequestError("Params vmem_size parse error") + } } } } + if self.Status == VM_RUNNING && (cpuChanged || memChanged) && self.GetDriver().NeedStopForChangeSpec() { + return nil, httperrors.NewInvalidStatusError("cannot change CPU/Memory spec in status %s", self.Status) + } + + if addCpu < 0 { + addCpu = 0 + } + if addMem < 0 { + addMem = 0 + } + disks := self.GetDisks() var addDisk int var diskIdx = 1 @@ -1562,7 +1732,7 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if resizeDisks.Length() > 0 { confs.Add(resizeDisks, "resize") } - if jsonutils.QueryBoolean(data, "auto_start", false) { + if self.Status != VM_RUNNING && jsonutils.QueryBoolean(data, "auto_start", false) { confs.Add(jsonutils.NewBool(true), "auto_start") } @@ -1584,6 +1754,7 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T return nil, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err) } } + if newDisks.Length() > 0 { err := self.CreateDisksOnHost(ctx, userCred, host, newDisks, pendingUsage, false) if err != nil { @@ -1953,7 +2124,7 @@ func (self *SGuest) PerformAssociateEip(ctx context.Context, userCred mcclient.T params.Add(jsonutils.NewString(self.Id), "instance_id") params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") - err = eip.StartEipAssociateTask(ctx, userCred, params) + err = eip.StartEipAssociateTask(ctx, userCred, params, "") return nil, err } @@ -1974,7 +2145,9 @@ func (self *SGuest) PerformDissociateEip(ctx context.Context, userCred mcclient. self.SetStatus(userCred, VM_DISSOCIATE_EIP, "associate eip") - err = eip.StartEipDissociateTask(ctx, userCred, "") + autoDelete := jsonutils.QueryBoolean(data, "auto_delete", false) + + err = eip.StartEipDissociateTask(ctx, userCred, autoDelete, "") if err != nil { log.Errorf("fail to start dissociate task %s", err) return nil, httperrors.NewGeneralError(err) @@ -2015,12 +2188,17 @@ func (self *SGuest) PerformCreateEip(ctx context.Context, userCred mcclient.Toke return nil, httperrors.NewInvalidStatusError("No cloudregion???") } - err = ElasticipManager.allocateEipAndAssociateVM(ctx, userCred, self, int(bw), chargeType, host.ManagerId, region.Id) + eipPendingUsage := &SQuota{Eip: 1} + err = QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), eipPendingUsage) if err != nil { - return nil, httperrors.NewGeneralError(err) + return nil, httperrors.NewOutOfQuotaError("Out of eip quota: %s", err) } - self.SetStatus(userCred, VM_ASSOCIATE_EIP, "allocate and associate EIP") + err = ElasticipManager.AllocateEipAndAssociateVM(ctx, userCred, self, int(bw), chargeType, eipPendingUsage) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, userCred.GetProjectId(), eipPendingUsage, eipPendingUsage) + return nil, httperrors.NewGeneralError(err) + } return nil, nil } @@ -2113,11 +2291,11 @@ func (manager *SGuestManager) PerformDirtyServerStart(ctx context.Context, userC // slave guest err := guest.GuestStartAndSyncToBackup(ctx, userCred, nil, "") return nil, err - } else if guest.BackupHostId != hostId { - // abandon guest - err := guest.StartUndeployGuestTask(ctx, userCred, "", hostId) - return nil, err } + // else { // 这里是清除这台机器最后的机会 + // err := guest.StartUndeployGuestTask(ctx, userCred, "", hostId) + // return nil, err + // } return nil, nil } @@ -2139,7 +2317,7 @@ func (self *SGuest) AllowPerformCreateBackup(ctx context.Context, userCred mccli func (self *SGuest) PerformCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { if len(self.BackupHostId) > 0 { - return nil, httperrors.NewBadRequestError("Already have create backup server") + return nil, httperrors.NewBadRequestError("Already have backup server") } if self.getDefaultStorageType() != STORAGE_LOCAL { return nil, httperrors.NewBadRequestError("Cannot create backup with shared storage") @@ -2148,7 +2326,7 @@ func (self *SGuest) PerformCreateBackup(ctx context.Context, userCred mcclient.T return nil, httperrors.NewBadRequestError("Backup only support hypervisor kvm") } if len(self.GetIsolatedDevices()) > 0 { - return nil, httperrors.NewBadRequestError("Cannot create backup with isolated degices") + return nil, httperrors.NewBadRequestError("Cannot create backup with isolated devices") } if self.GuestDisksHasSnapshot() { return nil, httperrors.NewBadRequestError("Cannot create backup with snapshot") @@ -2173,6 +2351,35 @@ func (self *SGuest) PerformCreateBackup(ctx context.Context, userCred mcclient.T return nil, nil } +func (self *SGuest) AllowPerformDeleteBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "delete-backup") +} + +func (self *SGuest) PerformDeleteBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if len(self.BackupHostId) == 0 { + return nil, httperrors.NewBadRequestError("Guest without backup") + } + backupHost := HostManager.FetchHostById(self.BackupHostId) + if backupHost == nil { + return nil, httperrors.NewNotFoundError("Guest backup host not found") + } + if backupHost.Status == HOST_OFFLINE && !jsonutils.QueryBoolean(data, "purge", false) { + return nil, httperrors.NewBadRequestError("Backup host is offline") + } + + taskData := jsonutils.NewDict() + taskData.Set("pruge", jsonutils.NewBool(jsonutils.QueryBoolean(data, "purge", false))) + taskData.Set("host_id", jsonutils.NewString(self.BackupHostId)) + if task, err := taskman.TaskManager.NewTask( + ctx, "GuestDeleteOnHostTask", self, userCred, taskData, "", "", nil); err != nil { + log.Errorf(err.Error()) + return nil, err + } else { + task.ScheduleRun(nil) + } + return nil, nil +} + func (self *SGuest) CreateBackupDisks(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { if task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateBackupDisksTask", self, userCred, nil, parentTaskId, "", nil); err != nil { log.Errorf(err.Error()) @@ -2197,6 +2404,18 @@ func (self *SGuest) StartCreateBackup(ctx context.Context, userCred mcclient.Tok return nil } +func (self *SGuest) AllowPerformMirrorJobFailed(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "mirror-job-failed") +} + +func (self *SGuest) PerformMirrorJobFailed(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if len(self.BackupHostId) == 0 { + return nil, nil + } else { + return nil, self.SetStatus(userCred, VM_MIRROR_FAIL, "OnSyncToBackup") + } +} + func (self *SGuest) AllowPerformSetExtraOption(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return db.IsAdminAllowPerform(userCred, self, "set-extra-option") } diff --git a/pkg/compute/models/guestdisks.go b/pkg/compute/models/guestdisks.go index 1cee788cc4..d051c6c22d 100644 --- a/pkg/compute/models/guestdisks.go +++ b/pkg/compute/models/guestdisks.go @@ -36,7 +36,7 @@ func init() { type SGuestdisk struct { SGuestJointsBase - DiskId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + DiskId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) ImagePath string `width:"256" charset:"ascii" nullable:"false" get:"user" create:"required"` // Column(VARCHAR(256, charset='ascii'), nullable=False) diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go index cb7e7845f8..3999161bdb 100644 --- a/pkg/compute/models/guestdrivers.go +++ b/pkg/compute/models/guestdrivers.go @@ -4,6 +4,7 @@ import ( "context" "time" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" @@ -19,11 +20,14 @@ type IGuestDriver interface { GetMaxVMemSizeGB() int GetMaxSecurityGroupCount() int + GetDefaultSysDiskBackend() string + GetMinimalSysDiskSizeGb() int + IsSupportedBillingCycle(bc billing.SBillingCycle) bool RequestRenewInstance(guest *SGuest, bc billing.SBillingCycle) (time.Time, error) - GetJsonDescAtHost(ctx context.Context, guest *SGuest, host *SHost) jsonutils.JSONObject + GetJsonDescAtHost(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, host *SHost) jsonutils.JSONObject ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) @@ -108,7 +112,7 @@ type IGuestDriver interface { AllowReconfigGuest() bool DoGuestCreateDisksTask(ctx context.Context, guest *SGuest, task taskman.ITask) error - RequestChangeVmConfig(ctx context.Context, guest *SGuest, task taskman.ITask, vcpuCount, vmemSize int64) error + RequestChangeVmConfig(ctx context.Context, guest *SGuest, task taskman.ITask, instanceType string, vcpuCount, vmemSize int64) error RequestGuestHotAddIso(ctx context.Context, guest *SGuest, path string, task taskman.ITask) error RequestRebuildRootDisk(ctx context.Context, guest *SGuest, task taskman.ITask) error @@ -118,6 +122,10 @@ type IGuestDriver interface { RequestDeleteSnapshot(ctx context.Context, guest *SGuest, task taskman.ITask, params *jsonutils.JSONDict) error RequestReloadDiskSnapshot(ctx context.Context, guest *SGuest, task taskman.ITask, params *jsonutils.JSONDict) error RequestSyncToBackup(ctx context.Context, guest *SGuest, task taskman.ITask) error + + IsSupportEip() bool + + NeedStopForChangeSpec() bool } var guestDrivers map[string]IGuestDriver diff --git a/pkg/compute/models/guestjoints.go b/pkg/compute/models/guestjoints.go index b288e3e70c..4f4371e4b9 100644 --- a/pkg/compute/models/guestjoints.go +++ b/pkg/compute/models/guestjoints.go @@ -22,7 +22,7 @@ func NewGuestJointsManager(dt interface{}, tableName string, keyword string, key type SGuestJointsBase struct { db.SVirtualJointResourceBase - GuestId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + GuestId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) } func (self *SGuestJointsBase) getGuest() *SGuest { diff --git a/pkg/compute/models/guestnetworks.go b/pkg/compute/models/guestnetworks.go index 57fc3b67a9..236e893786 100644 --- a/pkg/compute/models/guestnetworks.go +++ b/pkg/compute/models/guestnetworks.go @@ -12,7 +12,6 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/regutils" "yunion.io/x/sqlchemy" @@ -20,10 +19,13 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient" ) const ( MAX_IFNAME_SIZE = 13 + + MAX_GUESTNIC_TO_SAME_NETWORK = 2 ) type SGuestnetworkManager struct { @@ -49,15 +51,15 @@ func init() { type SGuestnetwork struct { SGuestJointsBase - NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) - MacAddr string `width:"32" charset:"ascii" nullable:"false" list:"user"` // Column(VARCHAR(32, charset='ascii'), nullable=False) - IpAddr string `width:"16" charset:"ascii" nullable:"false" list:"user"` // Column(VARCHAR(16, charset='ascii'), nullable=True) - Ip6Addr string `width:"64" charset:"ascii" nullable:"true" list:"user"` // Column(VARCHAR(64, charset='ascii'), nullable=True) - Driver string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user"` // Column(VARCHAR(16, charset='ascii'), nullable=True) - BwLimit int `nullable:"false" default:"0" list:"user"` // Column(Integer, nullable=False, default=0) # Mbps - Index int8 `nullable:"false" default:"0" list:"user" update:"user"` // Column(TINYINT, nullable=False, default=0) - Virtual bool `default:"false" list:"user"` // Column(Boolean, default=False) - Ifname string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user"` // Column(VARCHAR(16, charset='ascii'), nullable=True) + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" ` // Column(VARCHAR(36, charset='ascii'), nullable=False) + MacAddr string `width:"32" charset:"ascii" nullable:"false" list:"user"` // Column(VARCHAR(32, charset='ascii'), nullable=False) + IpAddr string `width:"16" charset:"ascii" nullable:"false" list:"user"` // Column(VARCHAR(16, charset='ascii'), nullable=True) + Ip6Addr string `width:"64" charset:"ascii" nullable:"true" list:"user"` // Column(VARCHAR(64, charset='ascii'), nullable=True) + Driver string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user"` // Column(VARCHAR(16, charset='ascii'), nullable=True) + BwLimit int `nullable:"false" default:"0" list:"user"` // Column(Integer, nullable=False, default=0) # Mbps + Index int8 `nullable:"false" default:"0" list:"user" update:"user"` // Column(TINYINT, nullable=False, default=0) + Virtual bool `default:"false" list:"user"` // Column(Boolean, default=False) + Ifname string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user"` // Column(VARCHAR(16, charset='ascii'), nullable=True) } func (joint *SGuestnetwork) Master() db.IStandaloneModel { @@ -119,7 +121,7 @@ func (manager *SGuestnetworkManager) GenerateMac(netId string, suggestion string func (manager *SGuestnetworkManager) newGuestNetwork(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, network *SNetwork, index int8, address string, mac string, driver string, bwLimit int, virtual bool, reserved bool, - allocDir IPAddlocationDirection, requiredDesignatedIp bool) (*SGuestnetwork, error) { + allocDir IPAddlocationDirection, requiredDesignatedIp bool, ifName string) (*SGuestnetwork, error) { gn := SGuestnetwork{} gn.SetModelManager(GuestnetworkManager) @@ -147,7 +149,7 @@ func (manager *SGuestnetworkManager) newGuestNetwork(ctx context.Context, userCr gn.MacAddr = macAddr if !virtual { addrTable := network.GetUsedAddresses() - recentAddrTable := manager.getRecentlyReleasedIPAddresses(network.Id, time.Duration(network.AllocTimoutSeconds)*time.Second) + recentAddrTable := manager.getRecentlyReleasedIPAddresses(network.Id, network.getAllocTimoutDuration()) ipAddr, err := network.GetFreeIP(ctx, userCred, addrTable, recentAddrTable, address, allocDir, reserved) if err != nil { return nil, err @@ -158,7 +160,15 @@ func (manager *SGuestnetworkManager) newGuestNetwork(ctx context.Context, userCr gn.IpAddr = ipAddr } ifTable := network.GetUsedIfnames() - ifName := gn.GetFreeIfname(network, ifTable) + if len(ifName) > 0 { + if _, ok := ifTable[ifName]; ok { + ifName = "" + log.Infof("ifname %s has been used, to release ...", ifName) + } + } + if len(ifName) == 0 { + ifName = gn.GetFreeIfname(network, ifTable) + } gn.Ifname = ifName err := manager.TableSpec().Insert(&gn) if err != nil { @@ -314,18 +324,10 @@ func (self *SGuestnetwork) ValidateUpdateData(ctx context.Context, userCred mccl return self.SJointResourceBase.ValidateUpdateData(ctx, userCred, query, data) } -func (manager *SGuestnetworkManager) DeleteGuestNics(ctx context.Context, guest *SGuest, userCred mcclient.TokenCredential, network *SNetwork, reserve bool) error { - q := manager.Query().Equals("guest_id", guest.Id) - if network != nil { - q = q.Equals("network_id", network.Id) - } - gns := make([]SGuestnetwork, 0) - err := db.FetchModelObjects(manager, q, &gns) - if err != nil { - log.Errorf("%s", err) - return err - } - for _, gn := range gns { +func (manager *SGuestnetworkManager) DeleteGuestNics(ctx context.Context, userCred mcclient.TokenCredential, gns []SGuestnetwork, reserve bool) error { + for i := range gns { + gn := gns[i] + guest := gn.GetGuest() net := gn.GetNetwork() if regutils.MatchIP4Addr(gn.IpAddr) || regutils.MatchIP6Addr(gn.Ip6Addr) { net.updateDnsRecord(&gn, false) @@ -336,7 +338,7 @@ func (manager *SGuestnetworkManager) DeleteGuestNics(ctx context.Context, guest } // ?? // gn.Delete(ctx, userCred) - err = gn.Delete(ctx, userCred) + err := gn.Delete(ctx, userCred) if err != nil { log.Errorf("%s", err) } @@ -500,7 +502,11 @@ func (self *SGuestnetwork) GetVirtualIPs() []string { net := self.GetNetwork() for _, guestgroup := range guest.GetGroups() { group := guestgroup.GetGroup() - for _, groupnetwork := range group.GetNetworks() { + groupnets, err := group.GetNetworks() + if err != nil { + continue + } + for _, groupnetwork := range groupnets { gnet := groupnetwork.GetNetwork() if gnet.WireId == net.WireId { ips = append(ips, groupnetwork.IpAddr) @@ -590,3 +596,19 @@ func (manager *SGuestnetworkManager) getRecentlyReleasedIPAddresses(networkId st } return ret } + +func (manager *SGuestnetworkManager) FilterByParams(q *sqlchemy.SQuery, params jsonutils.JSONObject) *sqlchemy.SQuery { + macStr := jsonutils.GetAnyString(params, []string{"mac", "mac_addr"}) + if len(macStr) > 0 { + q = q.Filter(sqlchemy.Equals(q.Field("mac_addr"), macStr)) + } + ipStr := jsonutils.GetAnyString(params, []string{"ipaddr", "ip_addr", "ip"}) + if len(ipStr) > 0 { + q = q.Filter(sqlchemy.Equals(q.Field("ip_addr"), ipStr)) + } + ip6Str := jsonutils.GetAnyString(params, []string{"ip6addr", "ip6_addr", "ip6"}) + if len(ip6Str) > 0 { + q = q.Filter(sqlchemy.Equals(q.Field("ip6_addr"), ip6Str)) + } + return q +} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index b009681319..8cef315626 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -485,6 +485,23 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ } q = q.Filter(cond(q.Field("id"), sgq)) } + + orderByDisk, _ := queryDict.GetString("order_by_disk") + if orderByDisk == "asc" { + guestdisks := GuestdiskManager.Query().SubQuery() + disks := DiskManager.Query().SubQuery() + q.AppendField(sqlchemy.SUM("disks_size", disks.Field("disk_size"))) + q = q.Join(guestdisks, sqlchemy.Equals(q.Field("id"), guestdisks.Field("guest_id"))). + Join(disks, sqlchemy.Equals(guestdisks.Field("disk_id"), disks.Field("id"))). + Asc(q.Field("disks_size")).GroupBy(q.Field("id")) + } else if orderByDisk == "desc" { + guestdisks := GuestdiskManager.Query().SubQuery() + disks := DiskManager.Query().SubQuery() + q.AppendField(sqlchemy.SUM("disks_size", disks.Field("disk_size"))) + q = q.Join(guestdisks, sqlchemy.Equals(q.Field("id"), guestdisks.Field("guest_id"))). + Join(disks, sqlchemy.Equals(guestdisks.Field("disk_id"), disks.Field("id"))). + Desc(q.Field("disks_size")).GroupBy(q.Field("id")) + } return q, nil } @@ -572,26 +589,60 @@ func (guest *SGuest) GetGuestDisk(diskId string) *SGuestdisk { return guestdisk.(*SGuestdisk) } -func (guest *SGuest) GetNetworksQuery() *sqlchemy.SQuery { - return GuestnetworkManager.Query().Equals("guest_id", guest.Id) +func (guest *SGuest) GetNetworksQuery(netId string) *sqlchemy.SQuery { + q := GuestnetworkManager.Query().Equals("guest_id", guest.Id) + if len(netId) > 0 { + q = q.Equals("network_id", netId) + } + return q } func (guest *SGuest) NetworkCount() int { - return guest.GetNetworksQuery().Count() + return guest.GetNetworksQuery("").Count() } -func (guest *SGuest) GetNetworks() []SGuestnetwork { +func (guest *SGuest) GetNetworks(netId string) ([]SGuestnetwork, error) { guestnics := make([]SGuestnetwork, 0) - q := guest.GetNetworksQuery().Asc("index") + q := guest.GetNetworksQuery(netId).Asc("index") err := db.FetchModelObjects(GuestnetworkManager, q, &guestnics) if err != nil { log.Errorf("GetNetworks error: %s", err) + return nil, err } - return guestnics + return guestnics, nil +} + +func (guest *SGuest) getNetworkByIpOrMac(ipAddr string, macAddr string) (*SGuestnetwork, error) { + q := guest.GetNetworksQuery("") + if len(ipAddr) > 0 { + q = q.Equals("ip_addr", ipAddr) + } + if len(macAddr) > 0 { + q = q.Equals("mac_addr", macAddr) + } + + guestnic := SGuestnetwork{} + err := q.First(&guestnic) + if err != nil { + return nil, err + } + guestnic.SetModelManager(GuestnetworkManager) + return &guestnic, nil +} + +func (guest *SGuest) GetNetworkByIp(ipAddr string) (*SGuestnetwork, error) { + return guest.getNetworkByIpOrMac(ipAddr, "") +} + +func (guest *SGuest) GetNetworkByMac(macAddr string) (*SGuestnetwork, error) { + return guest.getNetworkByIpOrMac("", macAddr) } func (guest *SGuest) IsNetworkAllocated() bool { - guestnics := guest.GetNetworks() + guestnics, err := guest.GetNetworks("") + if err != nil { + return false + } for _, gn := range guestnics { if !gn.IsAllocated() { return false @@ -724,7 +775,7 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m var err error var hypervisor string - var rootStorageType string + // var rootStorageType string var osProf osprofile.SOSProfile hypervisor, _ = data.GetString("hypervisor") if hypervisor != HYPERVISOR_CONTAINER { @@ -745,10 +796,10 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m return nil, httperrors.NewBadRequestError("Miss operating system???") } - if len(diskConfig.Backend) == 0 { - diskConfig.Backend = STORAGE_LOCAL - } - rootStorageType = diskConfig.Backend + // if len(diskConfig.Backend) == 0 { + // diskConfig.Backend = STORAGE_LOCAL + // } + // rootStorageType = diskConfig.Backend data.Add(jsonutils.Marshal(diskConfig), "disk.0") @@ -827,7 +878,7 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m dataDiskDefs := make([]string, 0) if sku != nil && sku.AttachedDiskCount > 0 { for i := 0; i < sku.AttachedDiskCount; i += 1 { - dataDiskDefs = append(dataDiskDefs, fmt.Sprintf("%dgb:%s", sku.AttachedDiskSizeGB, sku.AttachedDiskType)) + dataDiskDefs = append(dataDiskDefs, fmt.Sprintf("%dg:%s", sku.AttachedDiskSizeGB, sku.AttachedDiskType)) } } @@ -841,6 +892,18 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m dataDiskDefs = append(dataDiskDefs, diskJson) } + rootDiskConfig, err := parseDiskInfo(ctx, userCred, jsonArray[0]) + if err != nil { + return nil, httperrors.NewGeneralError(err) // should no error + } + if len(rootDiskConfig.Backend) == 0 { + rootDiskConfig.Backend = GetDriver(hypervisor).GetDefaultSysDiskBackend() + } + if rootDiskConfig.SizeMb == 0 { + rootDiskConfig.SizeMb = GetDriver(hypervisor).GetMinimalSysDiskSizeGb() * 1024 + } + data.Set("disk.0", jsonutils.Marshal(rootDiskConfig)) + for i := 0; i < len(dataDiskDefs); i += 1 { diskConfig, err := parseDiskInfo(ctx, userCred, jsonutils.NewString(dataDiskDefs[i])) if err != nil { @@ -850,12 +913,12 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m return nil, httperrors.NewBadRequestError("Snapshot error: disk index %d > 0 but disk type is %s", i+1, DISK_TYPE_SYS) } if len(diskConfig.Backend) == 0 { - diskConfig.Backend = rootStorageType + diskConfig.Backend = rootDiskConfig.Backend } if len(diskConfig.Driver) == 0 { diskConfig.Driver = osProf.DiskDriver } - data.Add(jsonutils.Marshal(diskConfig), fmt.Sprintf("disk.%d", i+1)) + data.Set(fmt.Sprintf("disk.%d", i+1), jsonutils.Marshal(diskConfig)) } resourceTypeStr := jsonutils.GetAnyString(data, []string{"resource_type"}) @@ -946,6 +1009,42 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m data.Add(jsonutils.NewString("default"), "secgrp_id") } + eipStr, _ := data.GetString("eip") + eipBw, _ := data.Int("eip_bw") + if len(eipStr) > 0 || eipBw > 0 { + if !GetDriver(hypervisor).IsSupportEip() { + return nil, httperrors.NewNotImplementedError("eip not supported for %s", hypervisor) + } + if len(eipStr) > 0 { + eipObj, err := ElasticipManager.FetchByIdOrName(userCred, eipStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(ElasticipManager.Keyword(), eipStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + + eip := eipObj.(*SElasticip) + if eip.Status != EIP_STATUS_READY { + return nil, httperrors.NewInvalidStatusError("eip %s status invalid %s", eipStr, eip.Status) + } + if eip.IsAssociated() { + return nil, httperrors.NewResourceBusyError("eip %s has been associated", eipStr) + } + data.Set("eip_id", jsonutils.NewString(eipObj.GetId())) + + eipRegion := eip.GetRegion() + preferRegionId, _ := data.GetString("prefer_region_id") + if len(preferRegionId) > 0 && preferRegionId != eipRegion.Id { + return nil, httperrors.NewConflictError("cannot assoicate with eip %s: different region", eipStr) + } + data.Set("prefer_region_id", jsonutils.NewString(eipRegion.Id)) + } else { + // create new eip + } + } + /* TODO group @@ -1043,6 +1142,13 @@ func getGuestResourceRequirements(ctx context.Context, userCred mcclient.TokenCr vmemSize = vmemSize * 2 diskSize = diskSize * 2 } + + eipCnt := 0 + eipBw, _ := data.Int("eip_bw") + if eipBw > 0 { + eipCnt = 1 + } + return SQuota{ Cpu: int(vcpuCount) * count, Memory: int(vmemSize) * count, @@ -1052,6 +1158,7 @@ func getGuestResourceRequirements(ctx context.Context, userCred mcclient.TokenCr Bw: iBw * count, Ebw: eBw * count, IsolatedDevice: devCount * count, + Eip: eipCnt * count, } } @@ -1111,7 +1218,10 @@ func (guest *SGuest) GetGroups() []SGroupguest { func (self *SGuest) getBandwidth(isExit bool) int { bw := 0 - networks := self.GetNetworks() + networks, err := self.GetNetworks("") + if err != nil { + return bw + } if networks != nil && len(networks) > 0 { for i := 0; i < len(networks); i += 1 { if networks[i].IsExit() == isExit { @@ -1246,6 +1356,9 @@ func (self *SGuest) GetExtraDetails(ctx context.Context, userCred mcclient.Token osName := self.GetOS() if len(osName) > 0 { extra.Add(jsonutils.NewString(osName), "os_name") + if len(self.OsType) == 0 { + extra.Add(jsonutils.NewString(osName), "os_type") + } } if metaData, err := self.GetAllMetadata(userCred); err == nil { extra.Add(jsonutils.Marshal(metaData), "metadata") @@ -1372,8 +1485,12 @@ func (manager *SGuestManager) GetExportExtraKeys(ctx context.Context, query json } func (self *SGuest) getNetworksDetails() string { + guestnets, err := self.GetNetworks("") + if err != nil { + return "" + } var buf bytes.Buffer - for _, nic := range self.GetNetworks() { + for _, nic := range guestnets { buf.WriteString(nic.GetDetailedString()) buf.WriteString("\n") } @@ -1455,18 +1572,22 @@ func (self *SGuest) getKeypairName() string { return "" } -func (self *SGuest) getNotifyIps() []string { +func (self *SGuest) getNotifyIps() string { ips := self.getRealIPs() vips := self.getVirtualIPs() if vips != nil { ips = append(ips, vips...) } - return ips + return strings.Join(ips, ",") } func (self *SGuest) getRealIPs() []string { + guestnets, err := self.GetNetworks("") + if err != nil { + return nil + } ips := make([]string, 0) - for _, nic := range self.GetNetworks() { + for _, nic := range guestnets { if !nic.Virtual { ips = append(ips, nic.IpAddr) } @@ -1488,7 +1609,11 @@ func (self *SGuest) getVirtualIPs() []string { ips := make([]string, 0) for _, guestgroup := range self.GetGroups() { group := guestgroup.GetGroup() - for _, groupnetwork := range group.GetNetworks() { + groupnets, err := group.GetNetworks() + if err != nil { + continue + } + for _, groupnetwork := range groupnets { ips = append(ips, groupnetwork.IpAddr) } } @@ -1658,10 +1783,13 @@ func (self *SGuest) GetIsolatedDevices() []SIsolatedDevice { return IsolatedDeviceManager.findAttachedDevicesOfGuest(self) } -func (self *SGuest) syncWithCloudVM(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, extVM cloudprovider.ICloudVM, projectId string, projectSync bool) error { +func (self *SGuest) syncWithCloudVM(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, host *SHost, extVM cloudprovider.ICloudVM, projectId string, projectSync bool) error { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + recycle := false - if self.IsPrepaidRecycle() { + if provider.SupportPrepaidResources() && self.IsPrepaidRecycle() { recycle = true } @@ -1707,7 +1835,7 @@ func (self *SGuest) syncWithCloudVM(ctx context.Context, userCred mcclient.Token self.IsEmulated = extVM.IsEmulated() - if !recycle { + if provider.SupportPrepaidResources() && !recycle { self.BillingType = extVM.GetBillingType() self.ExpiredAt = extVM.GetExpiredAt() } @@ -1757,7 +1885,7 @@ func (self *SGuest) syncWithCloudVM(ctx context.Context, userCred mcclient.Token } } - if recycle { + if provider.SupportPrepaidResources() && recycle { vhost := self.GetHost() err = vhost.syncWithCloudPrepaidVM(extVM, host, projectSync) if err != nil { @@ -1768,7 +1896,7 @@ func (self *SGuest) syncWithCloudVM(ctx context.Context, userCred mcclient.Token return nil } -func (manager *SGuestManager) newCloudVM(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, extVM cloudprovider.ICloudVM, projectId string) (*SGuest, error) { +func (manager *SGuestManager) newCloudVM(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, host *SHost, extVM cloudprovider.ICloudVM, projectId string) (*SGuest, error) { guest := SGuest{} guest.SetModelManager(manager) @@ -1787,8 +1915,10 @@ func (manager *SGuestManager) newCloudVM(ctx context.Context, userCred mcclient. guest.IsEmulated = extVM.IsEmulated() - guest.BillingType = extVM.GetBillingType() - guest.ExpiredAt = extVM.GetExpiredAt() + if provider.SupportPrepaidResources() { + guest.BillingType = extVM.GetBillingType() + guest.ExpiredAt = extVM.GetExpiredAt() + } guest.HostId = host.Id @@ -1885,10 +2015,8 @@ func (manager *SGuestManager) TotalCount( return totalGuestResourceCount(projectId, rangeObj, status, hypervisors, includeSystem, pendingDelete, hostTypes, resourceTypes, providers) } -func (self *SGuest) detachNetwork(ctx context.Context, userCred mcclient.TokenCredential, network *SNetwork, reserve bool, deploy bool) error { - // Portmaps.delete_guest_network_portmaps(self, user_cred, - // network_id=net.id) - err := GuestnetworkManager.DeleteGuestNics(ctx, self, userCred, network, reserve) +func (self *SGuest) detachNetworks(ctx context.Context, userCred mcclient.TokenCredential, gns []SGuestnetwork, reserve bool, deploy bool) error { + err := GuestnetworkManager.DeleteGuestNics(ctx, userCred, gns, reserve) if err != nil { return err } @@ -1902,14 +2030,17 @@ func (self *SGuest) detachNetwork(ctx context.Context, userCred mcclient.TokenCr return nil } -func (self *SGuest) isAttach2Network(net *SNetwork) bool { +func (self *SGuest) getAttach2NetworkCount(net *SNetwork) int { q := GuestnetworkManager.Query() q = q.Equals("guest_id", self.Id).Equals("network_id", net.Id) - return q.Count() > 0 + return q.Count() } func (self *SGuest) getMaxNicIndex() int8 { - nics := self.GetNetworks() + nics, err := self.GetNetworks("") + if err != nil { + return -1 + } return int8(len(nics)) } @@ -1930,9 +2061,14 @@ func (self *SGuest) getOSProfile() osprofile.SOSProfile { return osProf } -func (self *SGuest) Attach2Network(ctx context.Context, userCred mcclient.TokenCredential, network *SNetwork, pendingUsage quotas.IQuota, - address string, mac string, driver string, bwLimit int, virtual bool, index int8, reserved bool, allocDir IPAddlocationDirection, requireDesignatedIP bool) error { - if self.isAttach2Network(network) { +func (self *SGuest) Attach2Network(ctx context.Context, userCred mcclient.TokenCredential, network *SNetwork, + pendingUsage quotas.IQuota, + address string, mac string, driver string, bwLimit int, virtual bool, index int8, + reserved bool, allocDir IPAddlocationDirection, requireDesignatedIP bool, ifName string) error { + /* + allow a guest attach to a network 2 times + */ + if self.getAttach2NetworkCount(network) > MAX_GUESTNIC_TO_SAME_NETWORK { return fmt.Errorf("Guest has been attached to network %s", network.Name) } if index < 0 { @@ -1947,7 +2083,7 @@ func (self *SGuest) Attach2Network(ctx context.Context, userCred mcclient.TokenC guestnic, err := GuestnetworkManager.newGuestNetwork(ctx, userCred, self, network, index, address, mac, driver, bwLimit, virtual, reserved, - allocDir, requireDesignatedIP) + allocDir, requireDesignatedIP, ifName) if err != nil { return err } @@ -2010,7 +2146,12 @@ func getCloudNicNetwork(vnic cloudprovider.ICloudNic, host *SHost) (*SNetwork, e func (self *SGuest) SyncVMNics(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, vnics []cloudprovider.ICloudNic) compare.SyncResult { result := compare.SyncResult{} - guestnics := self.GetNetworks() + guestnics, err := self.GetNetworks("") + if err != nil { + result.Error(err) + return result + } + removed := make([]sRemoveGuestnic, 0) adds := make([]sAddGuestnic, 0) @@ -2060,7 +2201,7 @@ func (self *SGuest) SyncVMNics(ctx context.Context, userCred mcclient.TokenCrede } for _, remove := range removed { - err := self.detachNetwork(ctx, userCred, remove.nic.GetNetwork(), remove.reserve, false) + err := self.detachNetworks(ctx, userCred, []SGuestnetwork{*remove.nic}, remove.reserve, false) if err != nil { result.DeleteError(err) } else { @@ -2089,7 +2230,7 @@ func (self *SGuest) SyncVMNics(ctx context.Context, userCred mcclient.TokenCrede } } err = self.Attach2Network(ctx, userCred, add.net, nil, add.nic.GetIP(), - add.nic.GetMAC(), add.nic.GetDriver(), 0, false, -1, add.reserve, IPAllocationDefault, true) + add.nic.GetMAC(), add.nic.GetDriver(), 0, false, -1, add.reserve, IPAllocationDefault, true, "") if err != nil { result.AddError(err) } else { @@ -2141,7 +2282,7 @@ type sSyncDiskPair struct { vdisk cloudprovider.ICloudDisk } -func (self *SGuest) SyncVMDisks(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, vdisks []cloudprovider.ICloudDisk, projectId string, projectSync bool) compare.SyncResult { +func (self *SGuest) SyncVMDisks(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, host *SHost, vdisks []cloudprovider.ICloudDisk, projectId string, projectSync bool) compare.SyncResult { result := compare.SyncResult{} newdisks := make([]sSyncDiskPair, 0) @@ -2149,7 +2290,7 @@ func (self *SGuest) SyncVMDisks(ctx context.Context, userCred mcclient.TokenCred if len(vdisks[i].GetGlobalId()) == 0 { continue } - disk, err := DiskManager.syncCloudDisk(ctx, userCred, vdisks[i], i, projectId, projectSync) + disk, err := DiskManager.syncCloudDisk(ctx, userCred, provider, vdisks[i], i, projectId, projectSync) if err != nil { log.Errorf("syncCloudDisk error: %v", err) result.Error(err) @@ -2386,7 +2527,7 @@ func (self *SGuest) attach2NamedNetworkDesc(ctx context.Context, userCred mcclie driver := self.GetDriver() net, mac, idx, allocDir := driver.GetNamedNetworkConfiguration(self, userCred, host, netConfig) if net != nil { - err := self.Attach2Network(ctx, userCred, net, pendingUsage, netConfig.Address, mac, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, idx, netConfig.Reserved, allocDir, false) + err := self.Attach2Network(ctx, userCred, net, pendingUsage, netConfig.Address, mac, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, idx, netConfig.Reserved, allocDir, false, netConfig.Ifname) if err != nil { return err } else { @@ -2570,9 +2711,9 @@ type SGuestNicCategory struct { func (self *SGuest) CategorizeNics() SGuestNicCategory { netCat := SGuestNicCategory{} - guestnics := self.GetNetworks() - if guestnics == nil { - log.Errorf("no nics for this server!!!") + guestnics, err := self.GetNetworks("") + if err != nil { + log.Errorf("no nics for this server!!! %s", err) return netCat } @@ -2610,7 +2751,11 @@ func (self *SGuest) LeaveAllGroups(ctx context.Context, userCred mcclient.TokenC func (self *SGuest) DetachAllNetworks(ctx context.Context, userCred mcclient.TokenCredential) error { // from clouds.models.portmaps import Portmaps // Portmaps.delete_guest_network_portmaps(self, user_cred) - return GuestnetworkManager.DeleteGuestNics(ctx, self, userCred, nil, false) + gns, err := self.GetNetworks("") + if err != nil { + return err + } + return GuestnetworkManager.DeleteGuestNics(ctx, userCred, gns, false) } func (self *SGuest) EjectIso(userCred mcclient.TokenCredential) bool { @@ -2682,10 +2827,10 @@ type SDeployConfig struct { Content string } -func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, host *SHost, params *jsonutils.JSONDict) *jsonutils.JSONDict { +func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, params *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { config := jsonutils.NewDict() - desc := self.GetDriver().GetJsonDescAtHost(ctx, self, host) + desc := self.GetDriver().GetJsonDescAtHost(ctx, userCred, self, host) config.Add(desc, "desc") deploys := make([]SDeployConfig, 0) @@ -2754,7 +2899,55 @@ func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, host *SHost, para config.Add(jsonutils.NewString(onFinish), "on_finish") - return config + if deployAction == "create" && !utils.IsInStringArray(self.Hypervisor, []string{HYPERVISOR_KVM, HYPERVISOR_BAREMETAL, HYPERVISOR_CONTAINER, HYPERVISOR_ESXI, HYPERVISOR_XEN}) { + nets, err := self.GetNetworks("") + if err != nil || len(nets) == 0 { + return nil, fmt.Errorf("failed to find network for guest %s: %s", self.Name, err) + } + net := nets[0].GetNetwork() + vpc := net.GetVpc() + registerVpcId := vpc.ExternalId + externalVpcId := vpc.ExternalId + switch self.Hypervisor { + case HYPERVISOR_ALIYUN, HYPERVISOR_AWS, HYPERVISOR_OPENSTACK, HYPERVISOR_HUAWEI: + break + case HYPERVISOR_QCLOUD: + registerVpcId = "normal" + case HYPERVISOR_AZURE: + registerVpcId, externalVpcId = "normal", "normal" + if strings.HasSuffix(host.Name, "-classic") { + registerVpcId, externalVpcId = "classic", "classic" + } + default: + return nil, fmt.Errorf("Unknown guest %s hypervisor %s for sync secgroup", self.Name, self.Hypervisor) + } + iregion, err := host.GetIRegion() + if err != nil { + return nil, fmt.Errorf("failed to get iregion for host %s error: %v", host.Name, err) + } + secgroupIds := jsonutils.NewArray() + secgroups := self.GetSecgroups() + for i, secgroup := range secgroups { + secgroupCache := SecurityGroupCacheManager.Register(ctx, userCred, secgroup.Id, registerVpcId, vpc.CloudregionId, vpc.ManagerId) + if secgroupCache == nil { + return nil, fmt.Errorf("failed to registor secgroupCache for secgroup: %s(%s), vpc: %s", secgroup.Name, secgroup.Id, vpc.Name) + } + + externalSecgroupId, err := iregion.SyncSecurityGroup(secgroupCache.ExternalId, externalVpcId, secgroup.Name, secgroup.Description, secgroup.GetSecRules("")) + if err != nil { + return nil, fmt.Errorf("SyncSecurityGroup fail %s", err) + } + if err := secgroupCache.SetExternalId(externalSecgroupId); err != nil { + return nil, fmt.Errorf("failed to set externalId for secgroup %s(%s) externalId %s: error: %v", secgroup.Name, secgroup.Id, externalSecgroupId, err) + } + secgroupIds.Add(jsonutils.NewString(externalSecgroupId)) + if i == 0 { + config.Add(jsonutils.NewString(externalSecgroupId), "desc", "external_secgroup_id") + } + } + config.Add(secgroupIds, "desc", "external_secgroup_ids") + } + return config, nil } func (self *SGuest) getVga() string { @@ -2825,7 +3018,9 @@ func (self *SGuest) GetJsonDescAtHypervisor(ctx context.Context, host *SHost) *j // nics, domain jsonNics := make([]jsonutils.JSONObject, 0) - nics := self.GetNetworks() + + nics, _ := self.GetNetworks("") + domain := options.Options.DNSDomain if nics != nil && len(nics) > 0 { for _, nic := range nics { @@ -3087,7 +3282,8 @@ func (self *SGuest) GetSpec(checkStatus bool) *jsonutils.JSONDict { spec.Set("disk", diskSpecs) // get nic spec - guestnics := self.GetNetworks() + guestnics, _ := self.GetNetworks("") + nicSpecs := jsonutils.NewArray() for _, guestnic := range guestnics { nicSpec := jsonutils.NewDict() @@ -3268,6 +3464,7 @@ func (self *SGuest) saveOsType(osType string) error { } func (self *SGuest) SaveDeployInfo(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) { + log.Infof("------SaveDeployInfo: %s", data.PrettyString()) info := make(map[string]interface{}) if data.Contains("os") { osName, _ := data.GetString("os") @@ -3424,6 +3621,22 @@ func (manager *SGuestManager) getExpiredPrepaidGuests() []SGuest { return guests } +func (self *SGuest) doExternalSync(ctx context.Context, userCred mcclient.TokenCredential) error { + host := self.GetHost() + if host == nil { + return fmt.Errorf("no host???") + } + ihost, iprovider, err := host.GetIHostAndProvider() + if err != nil { + return err + } + iVM, err := ihost.GetIVMById(self.ExternalId) + if err != nil { + return err + } + return self.syncWithCloudVM(ctx, userCred, iprovider, host, iVM, "", false) +} + func (manager *SGuestManager) DeleteExpiredPrepaidServers(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { guests := manager.getExpiredPrepaidGuests() if guests == nil { @@ -3431,6 +3644,12 @@ func (manager *SGuestManager) DeleteExpiredPrepaidServers(ctx context.Context, u } for i := 0; i < len(guests); i += 1 { // fake delete expired prepaid servers + if len(guests[i].ExternalId) > 0 { + err := guests[i].doExternalSync(ctx, userCred) + if err == nil && guests[i].IsValidPrePaid() { + continue + } + } guests[i].SetDisableDelete(false) guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false) } @@ -3598,7 +3817,7 @@ func (self *SGuest) getSchedDesc() jsonutils.JSONObject { } } - gns := self.GetNetworks() + gns, _ := self.GetNetworks("") if gns != nil { for i := 0; i < len(gns); i += 1 { desc.Add(jsonutils.NewString(fmt.Sprintf("%s:%s", gns[i].NetworkId, gns[i].IpAddr)), fmt.Sprintf("net.%d", i)) diff --git a/pkg/compute/models/guestsecgroups.go b/pkg/compute/models/guestsecgroups.go index cac51429fd..2c59c62132 100644 --- a/pkg/compute/models/guestsecgroups.go +++ b/pkg/compute/models/guestsecgroups.go @@ -33,7 +33,7 @@ func init() { type SGuestsecgroup struct { SGuestJointsBase - SecgroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + SecgroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) } func (self *SGuestsecgroup) getSecgroup() *SSecurityGroup { diff --git a/pkg/compute/models/helper.go b/pkg/compute/models/helper.go index ee2ea97726..f573990cd4 100644 --- a/pkg/compute/models/helper.go +++ b/pkg/compute/models/helper.go @@ -84,6 +84,13 @@ func ValidateScheduleCreateData(ctx context.Context, userCred mcclient.TokenCred if err != nil { return nil, err } + + data.Set("prefer_baremetal_id", jsonutils.NewString(baremetal.Id)) + data.Set("prefer_host_id", jsonutils.NewString(baremetal.Id)) + zone := baremetal.GetZone() + data.Set("prefer_zone_id", jsonutils.NewString(zone.Id)) + region := zone.GetRegion() + data.Set("prefer_region_id", jsonutils.NewString(region.Id)) } else { schedtags := make(map[string]string) if data.Contains("aggregate_strategy") { @@ -121,9 +128,11 @@ func ValidateScheduleCreateData(ctx context.Context, userCred mcclient.TokenCred } } wire := wireObj.(*SWire) - data.Add(jsonutils.NewString(wire.Id), "prefer_wire_id") + data.Set("prefer_wire_id", jsonutils.NewString(wire.Id)) zone := wire.GetZone() - data.Add(jsonutils.NewString(zone.Id), "prefer_zone_id") + data.Set("prefer_zone_id", jsonutils.NewString(zone.Id)) + region := zone.GetRegion() + data.Set("prefer_region_id", jsonutils.NewString(region.Id)) } else if data.Contains("prefer_zone") { zoneStr, _ := data.GetString("prefer_zone") zoneObj, err := ZoneManager.FetchById(zoneStr) @@ -135,7 +144,21 @@ func ValidateScheduleCreateData(ctx context.Context, userCred mcclient.TokenCred } } zone := zoneObj.(*SZone) - data.Add(jsonutils.NewString(zone.Id), "prefer_zone_id") + data.Set("prefer_zone_id", jsonutils.NewString(zone.Id)) + region := zone.GetRegion() + data.Set("prefer_region_id", jsonutils.NewString(region.Id)) + } else if data.Contains("prefer_region") { + regionStr, _ := data.GetString("prefer_region") + regionObj, err := CloudregionManager.FetchById(regionStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("Region %s not found", regionStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + region := regionObj.(*SCloudregion) + data.Set("prefer_region_id", jsonutils.NewString(region.Id)) } } diff --git a/pkg/compute/models/host_recycle.go b/pkg/compute/models/host_recycle.go index cdec488667..7d6d531f3f 100644 --- a/pkg/compute/models/host_recycle.go +++ b/pkg/compute/models/host_recycle.go @@ -136,9 +136,9 @@ func (self *SGuest) doPrepaidRecycleNoLock(ctx context.Context, userCred mcclien fakeHost.IsMaintenance = false fakeHost.ResourceType = HostResourceTypePrepaidRecycle - guestnics := self.GetNetworks() - if len(guestnics) == 0 { - msg := "no network info on guest????" + guestnics, err := self.GetNetworks("") + if err != nil || len(guestnics) == 0 { + msg := fmt.Sprintf("no network info on guest???? %s", err) log.Errorf(msg) return fmt.Errorf(msg) } @@ -159,7 +159,7 @@ func (self *SGuest) doPrepaidRecycleNoLock(ctx context.Context, userCred mcclien fakeHost.IsEmulated = true fakeHost.Description = "fake host for prepaid vm recycling" - err := HostManager.TableSpec().Insert(&fakeHost) + err = HostManager.TableSpec().Insert(&fakeHost) if err != nil { log.Errorf("fail to insert fake host %s", err) return err @@ -523,7 +523,10 @@ func (host *SHost) IsPrepaidRecycle() bool { } func (self *SHost) BorrowIpAddrsFromGuest(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest) error { - guestnics := guest.GetNetworks() + guestnics, err := guest.GetNetworks("") + if err != nil { + return err + } for i := 0; i < len(guestnics); i += 1 { err := guestnics[i].Detach(ctx, userCred) if err != nil { @@ -538,7 +541,7 @@ func (self *SHost) BorrowIpAddrsFromGuest(ctx context.Context, userCred mcclient return fmt.Errorf(msg) } - err = self.EnableNetif(ctx, userCred, netif, "", guestnics[i].IpAddr, "", false, false) + err = self.EnableNetif(ctx, userCred, netif, "", guestnics[i].IpAddr, "", "", false, false) if err != nil { log.Errorf("fail to enable netif %s %s", guestnics[i].IpAddr, err) return err @@ -646,7 +649,13 @@ func (host *SHost) RebuildRecycledGuest(ctx context.Context, userCred mcclient.T return err } - err = guest.syncWithCloudVM(ctx, userCred, &oHost, extVM, "", false) + iprovider, err := oHost.GetDriver() + if err != nil { + log.Errorf("oHost.GetDriver fail %s", err) + return err + } + + err = guest.syncWithCloudVM(ctx, userCred, iprovider, &oHost, extVM, "", false) if err != nil { log.Errorf("guest.syncWithCloudVM fail %s", err) return err @@ -666,7 +675,7 @@ func (host *SHost) RebuildRecycledGuest(ctx context.Context, userCred mcclient.T log.Errorf("disk.SetExternalId fail %s", err) return err } - err = disk.syncWithCloudDisk(ctx, userCred, idisks[i], i, "", false) + err = disk.syncWithCloudDisk(ctx, userCred, iprovider, idisks[i], i, "", false) if err != nil { log.Errorf("disk.syncWithCloudDisk fail %s", err) return err diff --git a/pkg/compute/models/hostdrivers.go b/pkg/compute/models/hostdrivers.go index 50d09e3892..69735ec906 100644 --- a/pkg/compute/models/hostdrivers.go +++ b/pkg/compute/models/hostdrivers.go @@ -25,8 +25,12 @@ type IHostDriver interface { RequestRebuildDiskOnStorage(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, task taskman.ITask, content *jsonutils.JSONDict) error RequestDeallocateDiskOnHost(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, task taskman.ITask) error - RequestResizeDiskOnHostOnline(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, size int64, task taskman.ITask) error - RequestResizeDiskOnHost(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, size int64, task taskman.ITask) error + RequestDeallocateBackupDiskOnHost(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, task taskman.ITask) error + + //RequestResizeDiskOnHostOnline(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, size int64, task taskman.ITask) error + + RequestResizeDiskOnHost(ctx context.Context, host *SHost, storage *SStorage, disk *SDisk, guest *SGuest, size int64, task taskman.ITask) error + RequestDeleteSnapshotsWithStorage(ctx context.Context, host *SHost, snapshot *SSnapshot, task taskman.ITask) error RequestResetDisk(ctx context.Context, host *SHost, disk *SDisk, params *jsonutils.JSONDict, task taskman.ITask) error RequestCleanUpDiskSnapshots(ctx context.Context, host *SHost, disk *SDisk, params *jsonutils.JSONDict, task taskman.ITask) error @@ -36,6 +40,9 @@ type IHostDriver interface { FinishConvert(userCred mcclient.TokenCredential, host *SHost, guest *SGuest, hostType string) error ConvertFailed(host *SHost) error GetRaidScheme(host *SHost, raid string) (string, error) + + IsReachStoragecacheCapacityLimit(host *SHost, cachedImages []SCachedimage) bool + GetStoragecacheQuota(host *SHost) int } var hostDrivers map[string]IHostDriver diff --git a/pkg/compute/models/hostnetworks.go b/pkg/compute/models/hostnetworks.go index 6d5fbcaa44..6fe4663733 100644 --- a/pkg/compute/models/hostnetworks.go +++ b/pkg/compute/models/hostnetworks.go @@ -33,10 +33,10 @@ func init() { type SHostnetwork struct { SHostJointsBase - BaremetalId string `width:"36" charset:"ascii" nullable:"false" list:"admin" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) - NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"admin" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) - IpAddr string `width:"16" charset:"ascii" list:"admin"` // Column(VARCHAR(16, charset='ascii')) - MacAddr string `width:"18" charset:"ascii" list:"admin"` // Column(VARCHAR(18, charset='ascii')) + BaremetalId string `width:"36" charset:"ascii" nullable:"false" list:"admin"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"admin"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + IpAddr string `width:"16" charset:"ascii" list:"admin"` // Column(VARCHAR(16, charset='ascii')) + MacAddr string `width:"18" charset:"ascii" list:"admin"` // Column(VARCHAR(18, charset='ascii')) } func (bn *SHostnetwork) Master() db.IStandaloneModel { diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index cdf119fdf7..21dfb5f7cc 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -154,13 +154,13 @@ type SHost struct { // Status string = Column(VARCHAR(16, charset='ascii'), nullable=False, default=baremetalstatus.INIT) # status HostStatus string `width:"16" charset:"ascii" nullable:"false" default:"offline" list:"admin"` // Column(VARCHAR(16, charset='ascii'), nullable=False, server_default=HOST_OFFLINE, default=HOST_OFFLINE) - ZoneId string `width:"128" charset:"ascii" nullable:"false" list:"admin" create:"admin_optional"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) + ZoneId string `width:"128" charset:"ascii" nullable:"false" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) HostType string `width:"36" charset:"ascii" nullable:"false" list:"admin" update:"admin" create:"admin_required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) Version string `width:"64" charset:"ascii" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(64, charset='ascii')) - IsBaremetal bool `nullable:"true" default:"false" list:"admin" create:"admin_optional"` // Column(Boolean, nullable=True, default=False) + IsBaremetal bool `nullable:"true" default:"false" list:"admin" update:"admin" create:"admin_optional"` // Column(Boolean, nullable=True, default=False) IsMaintenance bool `nullable:"true" default:"false" list:"admin"` // Column(Boolean, nullable=True, default=False) @@ -1606,7 +1606,7 @@ func (self *SHost) newCloudHostWire(ctx context.Context, userCred mcclient.Token return err } -func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCredential, vms []cloudprovider.ICloudVM, projectId string, projectSync bool) ([]SGuest, []cloudprovider.ICloudVM, compare.SyncResult) { +func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCredential, iprovider cloudprovider.ICloudProvider, vms []cloudprovider.ICloudVM, projectId string, projectSync bool) ([]SGuest, []cloudprovider.ICloudVM, compare.SyncResult) { localVMs := make([]SGuest, 0) remoteVMs := make([]cloudprovider.ICloudVM, 0) syncResult := compare.SyncResult{} @@ -1634,7 +1634,7 @@ func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCrede } for i := 0; i < len(commondb); i += 1 { - err := commondb[i].syncWithCloudVM(ctx, userCred, self, commonext[i], projectId, projectSync) + err := commondb[i].syncWithCloudVM(ctx, userCred, iprovider, self, commonext[i], projectId, projectSync) if err != nil { syncResult.UpdateError(err) } else { @@ -1656,7 +1656,7 @@ func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCrede continue } } - new, err := GuestManager.newCloudVM(ctx, userCred, self, added[i], projectId) + new, err := GuestManager.newCloudVM(ctx, userCred, iprovider, self, added[i], projectId) if err != nil { syncResult.AddError(err) } else { @@ -1681,96 +1681,46 @@ func (self *SHost) getNetworkOfIPOnHost(ipAddr string) (*SNetwork, error) { return net, nil } -func (self *SHost) GetNetinterfaceWithNetworkAndCredential(netId string, userCred mcclient.TokenCredential, reserved bool) (*SNetInterface, *SNetwork) { - netif, net := self.getNetifWithNetworkAndCredential(netId, userCred, true, reserved) - if netif != nil { - return netif, net +func (self *SHost) GetNetinterfaceWithIdAndCredential(netId string, userCred mcclient.TokenCredential, reserved bool) (*SNetInterface, *SNetwork) { + netObj, err := NetworkManager.FetchById(netId) + if err != nil { + return nil, nil + } + net := netObj.(*SNetwork) + if net.getFreeAddressCount() == 0 && !reserved { + return nil, nil } - return self.getNetifWithNetworkAndCredential(netId, userCred, false, reserved) -} - -func (self *SHost) getNetifWithNetworkAndCredential(netId string, userCred mcclient.TokenCredential, isPublic bool, reserved bool) (*SNetInterface, *SNetwork) { netifs := self.GetNetInterfaces() - var maxFreeCnt = 0 - var maxFreeNet *SNetwork - var maxFreeNetif *SNetInterface for i := 0; i < len(netifs); i++ { if !netifs[i].IsUsableServernic() { continue } - wire := netifs[i].GetWire() - if wire != nil { - if isPublic { - nets, _ := wire.getPublicNetworks() - for _, net := range nets { - if net.Id == netId || net.GetName() == netId { - freeCnt := net.getFreeAddressCount() - if maxFreeNet == nil || maxFreeCnt < freeCnt { - maxFreeNetif = &netifs[i] - maxFreeNet = &net - } - } - } - } else { - nets, _ := wire.getPrivateNetworks(userCred) - for _, net := range nets { - if net.Id == netId || net.GetName() == netId { - freeCnt := net.getFreeAddressCount() - if maxFreeNet == nil || maxFreeCnt < freeCnt { - maxFreeNetif = &netifs[i] - maxFreeNet = &net - } - } - } - } + if netifs[i].WireId == net.WireId { + return &netifs[i], net } } - return maxFreeNetif, maxFreeNet + return nil, nil } func (self *SHost) GetNetworkWithIdAndCredential(netId string, userCred mcclient.TokenCredential, reserved bool) (*SNetwork, error) { - net, err := self.getNetworkWithIdAndCredential(netId, userCred, true, reserved) - if err == nil { - return net, nil - } - return self.getNetworkWithIdAndCredential(netId, userCred, false, reserved) -} - -func (self *SHost) getNetworkWithIdAndCredential(netId string, userCred mcclient.TokenCredential, isPublic bool, reserved bool) (*SNetwork, error) { networks := NetworkManager.Query().SubQuery() hostwires := HostwireManager.Query().SubQuery() hosts := HostManager.Query().SubQuery() q := networks.Query() - q = q.Join(hostwires, sqlchemy.AND(sqlchemy.Equals(hostwires.Field("wire_id"), networks.Field("wire_id")), - sqlchemy.IsFalse(hostwires.Field("deleted")))) - q = q.Join(hosts, sqlchemy.AND(sqlchemy.Equals(hosts.Field("id"), hostwires.Field("host_id")), - sqlchemy.IsFalse(hosts.Field("deleted")))) + q = q.Join(hostwires, sqlchemy.Equals(hostwires.Field("wire_id"), networks.Field("wire_id"))) + q = q.Join(hosts, sqlchemy.Equals(hosts.Field("id"), hostwires.Field("host_id"))) q = q.Filter(sqlchemy.Equals(hosts.Field("id"), self.Id)) - q = q.Filter(sqlchemy.OR(sqlchemy.Equals(networks.Field("id"), netId), - sqlchemy.Equals(networks.Field("name"), netId))) - if isPublic { - q = q.Filter(sqlchemy.IsTrue(networks.Field("is_public"))) - } else { - q = q.Filter(sqlchemy.Equals(networks.Field("tenant_id"), userCred.GetProjectId())) - } + q = q.Filter(sqlchemy.Equals(networks.Field("id"), netId)) - nets := make([]SNetwork, 0) - err := db.FetchModelObjects(NetworkManager, q, &nets) + net := SNetwork{} + net.SetModelManager(NetworkManager) + err := q.First(&net) if err != nil { return nil, err } - var maxFreeNet *SNetwork - maxFrees := 0 - for i := 0; i < len(nets); i += 1 { - freeCnt := nets[i].getFreeAddressCount() - if maxFreeNet == nil || maxFrees < freeCnt { - maxFrees = freeCnt - maxFreeNet = &nets[i] - } - } - if reserved || maxFrees > 0 { - return maxFreeNet, nil + if reserved || net.getFreeAddressCount() > 0 { + return &net, nil } return nil, fmt.Errorf("No IP address") } @@ -1934,9 +1884,14 @@ func (self *SHost) GetIZone() (cloudprovider.ICloudZone, error) { */ func (self *SHost) GetIHost() (cloudprovider.ICloudHost, error) { + host, _, err := self.GetIHostAndProvider() + return host, err +} + +func (self *SHost) GetIHostAndProvider() (cloudprovider.ICloudHost, cloudprovider.ICloudProvider, error) { provider, err := self.GetDriver() if err != nil { - return nil, fmt.Errorf("No cloudprovide for host: %s", err) + return nil, nil, fmt.Errorf("No cloudprovide for host: %s", err) } var iregion cloudprovider.ICloudRegion if provider.IsOnPremiseInfrastructure() { @@ -1946,20 +1901,20 @@ func (self *SHost) GetIHost() (cloudprovider.ICloudHost, error) { if region == nil { msg := "fail to find region of host???" log.Errorf(msg) - return nil, fmt.Errorf(msg) + return nil, nil, fmt.Errorf(msg) } iregion, err = provider.GetIRegionById(region.ExternalId) } if err != nil { log.Errorf("fail to find iregion: %s", err) - return nil, err + return nil, nil, err } ihost, err := iregion.GetIHostById(self.ExternalId) if err != nil { log.Errorf("fail to find ihost by id %s %s", self.ExternalId, err) - return nil, fmt.Errorf("fail to find ihost by id %s", err) + return nil, nil, fmt.Errorf("fail to find ihost by id %s", err) } - return ihost, nil + return ihost, provider, nil } func (self *SHost) GetIRegion() (cloudprovider.ICloudRegion, error) { @@ -1991,18 +1946,17 @@ func (self *SHost) GetBaremetalServer() *SGuest { if !self.IsBaremetal { return nil } - guestObj, err := db.NewModelObject(GuestManager) - if err != nil { - log.Errorf("%s", err) - return nil - } + guest := SGuest{} + guest.SetModelManager(GuestManager) q := GuestManager.Query().Equals("host_id", self.Id).Equals("hypervisor", HOST_TYPE_BAREMETAL) - err = q.First(guestObj) + err := q.First(&guest) if err != nil { - log.Errorf("query fail %s", err) + if err != sql.ErrNoRows { + log.Errorf("query fail %s", err) + } return nil } - return guestObj.(*SGuest) + return &guest } func (self *SHost) getSchedtags() []SSchedtag { @@ -2766,6 +2720,7 @@ func (self *SHost) StartPrepareTask(ctx context.Context, userCred mcclient.Token if len(onfinish) > 0 { data.Set("on_finish", jsonutils.NewString(onfinish)) } + self.SetStatus(userCred, BAREMETAL_PREPARE, "start prepare task") if task, err := taskman.TaskManager.NewTask(ctx, "BaremetalPrepareTask", self, userCred, data, parentTaskId, "", nil); err != nil { log.Errorf(err.Error()) return err @@ -2907,7 +2862,7 @@ func (self *SHost) addNetif(ctx context.Context, userCred mcclient.TokenCredenti bridge = fmt.Sprintf("br%s", sw.GetName()) } var isMaster = netif.NicType == NIC_TYPE_ADMIN - ihw, err := HostwireManager.FetchByIds(self.Id, sw.Id) + ihw, err := db.FetchJointByIds(HostwireManager, self.Id, sw.Id, nil) if err != nil { hw := &SHostwire{} hw.Bridge = bridge @@ -2933,7 +2888,7 @@ func (self *SHost) addNetif(ctx context.Context, userCred mcclient.TokenCredenti } } if len(ipAddr) > 0 { - err = self.EnableNetif(ctx, userCred, netif, "", ipAddr, "", reserve, requireDesignatedIp) + err = self.EnableNetif(ctx, userCred, netif, "", ipAddr, "", "", reserve, requireDesignatedIp) if err != nil { return httperrors.NewBadRequestError(err.Error()) } @@ -2960,20 +2915,22 @@ func (self *SHost) PerformEnableNetif(ctx context.Context, userCred mcclient.Tok network, _ := data.GetString("network") ipAddr, _ := data.GetString("ip_addr") allocDir, _ := data.GetString("alloc_dir") + netType, _ := data.GetString("net_type") reserve := jsonutils.QueryBoolean(data, "reserve", false) requireDesignatedIp := jsonutils.QueryBoolean(data, "require_designated_ip", false) - err := self.EnableNetif(ctx, userCred, netif, network, ipAddr, allocDir, reserve, requireDesignatedIp) + err := self.EnableNetif(ctx, userCred, netif, network, ipAddr, allocDir, netType, reserve, requireDesignatedIp) if err != nil { return nil, httperrors.NewBadRequestError(err.Error()) } return nil, nil } -func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, network, ipAddr, allocDir string, reserve, requireDesignatedIp bool) error { +func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, network, ipAddr, allocDir string, netType string, reserve, requireDesignatedIp bool) error { bn := netif.GetBaremetalNetwork() if bn != nil { return nil } + log.Errorf("==========EnableNetif %#v, net: %s, ipAddr: %s, allocDir: %s, reserve: %v, requireDesignatedIp: %v", netif, network, ipAddr, allocDir, reserve, requireDesignatedIp) var net *SNetwork var err error if len(ipAddr) > 0 { @@ -2989,7 +2946,7 @@ func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCrede if wire == nil { return fmt.Errorf("No wire attached") } - hw, err := HostwireManager.FetchByIds(self.Id, wire.Id) + hw, err := db.FetchJointByIds(HostwireManager, self.Id, wire.Id, nil) if hw == nil { return fmt.Errorf("host not attach to this wire") } @@ -3004,8 +2961,17 @@ func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCrede return fmt.Errorf("Network %s not reacheable on mac %s", network, netif.Mac) } } else { - net, err = wire.GetCandidatePrivateNetwork(userCred, false, SERVER_TYPE_BAREMETAL) - if err != nil || net == nil { + var netTypes []string + if len(netType) > 0 && netType != NETWORK_TYPE_BAREMETAL { + netTypes = []string{netType, NETWORK_TYPE_BAREMETAL} + } else { + netTypes = []string{NETWORK_TYPE_BAREMETAL} + } + net, err = wire.GetCandidatePrivateNetwork(userCred, false, netTypes) + if err != nil { + return fmt.Errorf("fail to find network %s", err) + } + if net == nil { return fmt.Errorf("No network found") } } @@ -3105,7 +3071,7 @@ func (self *SHost) RemoveNetif(ctx context.Context, userCred mcclient.TokenCrede log.Infof("Remove wire") others := self.GetNetifsOnWire(wire) if len(others) == 0 { - hw, _ := HostwireManager.FetchByIds(self.Id, wire.Id) + hw, _ := db.FetchJointByIds(HostwireManager, self.Id, wire.Id, nil) if hw != nil { db.OpsLog.LogDetachEvent(ctx, self, wire, userCred, jsonutils.NewString(fmt.Sprintf("disable netif %s", self.AccessMac))) log.Infof("Detach host wire because of remove netif %s", netif.Mac) @@ -3316,10 +3282,16 @@ func (self *SHost) PerformConvertHypervisor(ctx context.Context, userCred mcclie if err != nil { return nil, httperrors.NewNotAcceptableError("Convert error: %s", err.Error()) } - guest, err := GuestManager.DoCreate(ctx, userCred, data, params, GuestManager) + ownerProjId := userCred.GetProjectId() + guest, err := db.DoCreate(GuestManager, ctx, userCred, nil, params, ownerProjId) if err != nil { return nil, err } + func() { + lockman.LockObject(ctx, guest) + defer lockman.ReleaseObject(ctx, guest) + guest.PostCreate(ctx, userCred, ownerProjId, nil, params) + }() log.Infof("Host convert to %s", guest.GetName()) db.OpsLog.LogEvent(self, db.ACT_CONVERT_START, "", userCred) db.OpsLog.LogEvent(guest, db.ACT_CREATE, "Convert hypervisor", userCred) @@ -3502,7 +3474,7 @@ func (host *SHost) SyncHostExternalNics(ctx context.Context, userCred mcclient.T for i := 0; i < len(enables); i += 1 { netif := host.GetNetInterface(enables[i].GetMac()) - err = host.EnableNetif(ctx, userCred, netif, "", enables[i].GetIpAddr(), "", false, true) + err = host.EnableNetif(ctx, userCred, netif, "", enables[i].GetIpAddr(), "", "", false, true) if err != nil { result.AddError(err) } else { diff --git a/pkg/compute/models/hostschedtags.go b/pkg/compute/models/hostschedtags.go index fed5a42b24..c909a6efdb 100644 --- a/pkg/compute/models/hostschedtags.go +++ b/pkg/compute/models/hostschedtags.go @@ -32,8 +32,8 @@ func init() { type SHostschedtag struct { SHostJointsBase - HostId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required" key_index:"true"` // Column(VARCHAR(36, charset='ascii'), nullable=False) - SchedtagId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required" key_index:"true"` // =Column(VARCHAR(36, charset='ascii'), nullable=False) + HostId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + SchedtagId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // =Column(VARCHAR(36, charset='ascii'), nullable=False) } func (joint *SHostschedtag) Master() db.IStandaloneModel { diff --git a/pkg/compute/models/hoststorages.go b/pkg/compute/models/hoststorages.go index 5c2d7381c0..a42ccd736a 100644 --- a/pkg/compute/models/hoststorages.go +++ b/pkg/compute/models/hoststorages.go @@ -42,8 +42,8 @@ type SHoststorage struct { MountPoint string `width:"256" charset:"ascii" nullable:"false" list:"admin" update:"admin" create:"required"` // Column(VARCHAR(256, charset='ascii'), nullable=False) - HostId string `width:"36" charset:"ascii" nullable:"false" list:"admin" key_index:"true" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) - StorageId string `width:"36" charset:"ascii" nullable:"false" list:"admin" key_index:"true" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + HostId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) + StorageId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=False) Config *jsonutils.JSONArray `nullable:"true" get:"admin"` // Column(JSONEncodedDict, nullable=True) RealCapacity int `nullable:"true" list:"admin"` // Column(Integer, nullable=True) diff --git a/pkg/compute/models/hostwires.go b/pkg/compute/models/hostwires.go index ae1415c2ed..af5f36013f 100644 --- a/pkg/compute/models/hostwires.go +++ b/pkg/compute/models/hostwires.go @@ -38,8 +38,8 @@ type SHostwire struct { IsMaster bool `nullable:"true" default:"false" update:"admin" create:"admin_optional"` // Column(Boolean, nullable=True, default=False) MacAddr string `width:"18" charset:"ascii" list:"admin" update:"admin" create:"admin_required"` // Column(VARCHAR(18, charset='ascii')) - HostId string `width:"128" charset:"ascii" nullable:"false" list:"admin" create:"admin_required" key_index:"true"` // = Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) - WireId string `width:"128" charset:"ascii" nullable:"false" list:"admin" create:"admin_required" key_index:"true"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) + HostId string `width:"128" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // = Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) + WireId string `width:"128" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) } func (joint *SHostwire) Master() db.IStandaloneModel { diff --git a/pkg/compute/models/images.go b/pkg/compute/models/images.go deleted file mode 100644 index 37b73e236a..0000000000 --- a/pkg/compute/models/images.go +++ /dev/null @@ -1,29 +0,0 @@ -package models - -import "time" - -const ( - IMAGE_STATUS_ACTIVE = "active" - IMAGE_STATUS_QUEUED = "queued" - IMAGE_STATUS_KILLED = "killed" - IMAGE_STATUS_DELETED = "deleted" -) - -type SImage struct { - Checksum string - ContainerFormat string - CreatedAt time.Time - Deleted bool - DiskFormat string - Id string - IsPublic bool - MinDisk int - MinRam int - Name string - Owner string - Properties map[string]string - Protected bool - Size int - Status string - UpdatedAt time.Time -} diff --git a/pkg/compute/models/initdb.go b/pkg/compute/models/initdb.go index af51d9f58c..693031881c 100644 --- a/pkg/compute/models/initdb.go +++ b/pkg/compute/models/initdb.go @@ -22,6 +22,10 @@ func InitDB() error { NetworkManager, LoadbalancerCertificateManager, LoadbalancerManager, + LoadbalancerListenerManager, + LoadbalancerListenerRuleManager, + LoadbalancerBackendGroupManager, + LoadbalancerBackendManager, } { err := manager.InitializeData() if err != nil { diff --git a/pkg/compute/models/loadbalancer_const.go b/pkg/compute/models/loadbalancer_const.go index 77b83b2106..f6c7b597ba 100644 --- a/pkg/compute/models/loadbalancer_const.go +++ b/pkg/compute/models/loadbalancer_const.go @@ -26,14 +26,24 @@ const ( LB_STATUS_ENABLED = "enabled" LB_STATUS_DISABLED = "disabled" - LB_STATUS_INIT = "init" - LB_STATUS_UNKNOWN = "unknown" - LB_STATUS_RUNNING = "running" - LB_STATUS_STOPPED = "stopped" - LB_STATUS_CONFIGURING = "configuring" // config changes pending - LB_STATUS_STOPPING = "stopping" - LB_STATUS_DELETE_PENDING = "delete_pending" - LB_STATUS_ERROR = "error" // bad things happen + LB_STATUS_INIT = "init" + + LB_CREATING = "creating" + LB_CREATE_FAILED = "create_failed" + + LB_SYNC_CONF = "sync_conf" + LB_SYNC_CONF_FAILED = "sync_conf_failed" + + LB_SYNC_STATUS = "sync_status" + LB_SYNC_STATUS_FAILED = "sync_status_failed" + + LB_STATUS_DELETING = "deleting" + LB_STATUS_DELETE_FAILED = "delete_failed" + + LB_STATUS_START_FAILED = "start_failed" + LB_STATUS_STOP_FAILED = "stop_failed" + + LB_STATUS_UNKNOWN = "unknown" ) var LB_STATUS_SPEC = validators.NewChoices( @@ -56,15 +66,6 @@ var LB_BACKENDGROUP_TYPE = validators.NewChoices( LB_BACKENDGROUP_TYPE_MASTER_SLAVE, ) -var LB_STATUS_RUNTIME = validators.NewChoices( - LB_STATUS_INIT, - LB_STATUS_CONFIGURING, - LB_STATUS_RUNNING, - LB_STATUS_STOPPING, - LB_STATUS_STOPPED, - LB_STATUS_ERROR, -) - // Load Balancer network type (vpc or classic) determines viable backend // servers (they should be from the same network type as the load balancer). // diff --git a/pkg/compute/models/loadbalanceracls.go b/pkg/compute/models/loadbalanceracls.go index b436cc8aa2..dbd069dbd2 100644 --- a/pkg/compute/models/loadbalanceracls.go +++ b/pkg/compute/models/loadbalanceracls.go @@ -15,6 +15,7 @@ import ( "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -103,7 +104,7 @@ type SLoadbalancerAcl struct { db.SSharableVirtualResourceBase SManagedResourceBase - CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"optional"` + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"required"` AclEntries *SLoadbalancerAclEntries `list:"user" update:"user" create:"required"` } @@ -125,7 +126,17 @@ func (man *SLoadbalancerAclManager) ValidateCreateData(ctx context.Context, user if err != nil { return nil, err } - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + + regionV := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", ownerProjId) + regionV.Default("default") + if err := regionV.Validate(data); err != nil { + return nil, err + } + region := regionV.Model.(*SCloudregion) + return region.GetDriver().ValidateCreateLoadbalancerAclData(ctx, userCred, data) } func (lbacl *SLoadbalancerAcl) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -140,6 +151,60 @@ func (lbacl *SLoadbalancerAcl) ValidateUpdateData(ctx context.Context, userCred return lbacl.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) } +func (lbacl *SLoadbalancerAcl) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lbacl.SSharableVirtualResourceBase.PostUpdate(ctx, userCred, query, data) + lbacl.SetStatus(userCred, LB_SYNC_CONF, "") + lbacl.StartLoadBalancerAclSyncTask(ctx, userCred, "") +} + +func (lbacl *SLoadbalancerAcl) StartLoadBalancerAclSyncTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerAclSyncTask", lbacl, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lbacl *SLoadbalancerAcl) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lbacl.SSharableVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + + lbacl.SetStatus(userCred, LB_CREATING, "") + if err := lbacl.StartLoadBalancerAclCreateTask(ctx, userCred, ""); err != nil { + log.Errorf("Failed to create loadbalanceracl error: %v", err) + } +} + +func (lbacl *SLoadbalancerAcl) StartLoadBalancerAclCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerAclCreateTask", lbacl, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lbacl *SLoadbalancerAcl) GetRegion() *SCloudregion { + region, err := CloudregionManager.FetchById(lbacl.CloudregionId) + if err != nil { + log.Errorf("failed to find region for loadbalancer acl %s", lbacl.Name) + return nil + } + return region.(*SCloudregion) +} + +func (lbacl *SLoadbalancerAcl) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := lbacl.GetDriver() + if err != nil { + return nil, fmt.Errorf("No cloudprovide for lb %s: %s", lbacl.Name, err) + } + region := lbacl.GetRegion() + if region == nil { + return nil, fmt.Errorf("failed to find region for lb %s", lbacl.Name) + } + return provider.GetIRegionById(region.ExternalId) +} + func (lbacl *SLoadbalancerAcl) AllowPerformPatch(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { return lbacl.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, lbacl, "patch") } @@ -214,8 +279,28 @@ func (lbacl *SLoadbalancerAcl) ValidateDeleteCondition(ctx context.Context) erro return nil } -func (lbacl *SLoadbalancerAcl) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lbacl.DoPendingDelete(ctx, userCred) +func (lbacl *SLoadbalancerAcl) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lbacl, "purge") +} + +func (lbacl *SLoadbalancerAcl) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lbacl.StartLoadBalancerAclDeleteTask(ctx, userCred, parasm, "") +} + +func (lbacl *SLoadbalancerAcl) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + lbacl.SetStatus(userCred, LB_STATUS_DELETING, "") + return lbacl.StartLoadBalancerAclDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (lbacl *SLoadbalancerAcl) StartLoadBalancerAclDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerAclDeleteTask", lbacl, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lbacl *SLoadbalancerAcl) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { @@ -303,10 +388,9 @@ func (man *SLoadbalancerAclManager) newFromCloudLoadbalancerAcl(ctx context.Cont acl.ProjectId = provider.ProjectId } - aclEntries := extAcl.GetAclEntries() acl.AclEntries = &SLoadbalancerAclEntries{} - if err := aclEntries.Unmarshal(acl.AclEntries); err != nil { - return nil, err + for _, entry := range extAcl.GetAclEntries() { + *acl.AclEntries = append(*acl.AclEntries, &SLoadbalancerAclEntry{Cidr: entry.CIDR, Comment: entry.Comment}) } return &acl, man.TableSpec().Insert(&acl) } @@ -314,13 +398,14 @@ func (man *SLoadbalancerAclManager) newFromCloudLoadbalancerAcl(ctx context.Cont func (acl *SLoadbalancerAcl) SyncWithCloudLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, extAcl cloudprovider.ICloudLoadbalancerAcl, projectId string, projectSync bool) error { _, err := acl.GetModelManager().TableSpec().Update(acl, func() error { acl.Name = extAcl.GetName() - aclEntries := extAcl.GetAclEntries() - + acl.AclEntries = &SLoadbalancerAclEntries{} + for _, entry := range extAcl.GetAclEntries() { + *acl.AclEntries = append(*acl.AclEntries, &SLoadbalancerAclEntry{Cidr: entry.CIDR, Comment: entry.Comment}) + } if projectSync && len(projectId) > 0 { acl.ProjectId = projectId } - acl.AclEntries = &SLoadbalancerAclEntries{} - return aclEntries.Unmarshal(acl.AclEntries) + return nil }) return err } diff --git a/pkg/compute/models/loadbalancerbackendgroups.go b/pkg/compute/models/loadbalancerbackendgroups.go index 92860a0585..a7e7ace419 100644 --- a/pkg/compute/models/loadbalancerbackendgroups.go +++ b/pkg/compute/models/loadbalancerbackendgroups.go @@ -2,6 +2,7 @@ package models import ( "context" + "database/sql" "fmt" "yunion.io/x/jsonutils" @@ -11,8 +12,10 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -35,7 +38,9 @@ func init() { type SLoadbalancerBackendGroup struct { db.SVirtualResourceBase + SManagedResourceBase + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"optional"` Type string `width:"36" charset:"ascii" nullable:"false" list:"user" default:"normal" create:"optional"` LoadbalancerId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` } @@ -44,7 +49,7 @@ func (man *SLoadbalancerBackendGroupManager) PreDeleteSubs(ctx context.Context, subs := []SLoadbalancerBackendGroup{} db.FetchModelObjects(man, q, &subs) for _, sub := range subs { - sub.PreDelete(ctx, userCred) + sub.DoPendingDelete(ctx, userCred) } } @@ -70,14 +75,108 @@ func (man *SLoadbalancerBackendGroupManager) ValidateCreateData(ctx context.Cont if err != nil { return nil, err } - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + lb := lbV.Model.(*SLoadbalancer) + data.Set("manager_id", jsonutils.NewString(lb.ManagerId)) + data.Set("cloudregion_id", jsonutils.NewString(lb.CloudregionId)) + backends := []cloudprovider.SLoadbalancerBackend{} + if data.Contains("backends") { + if err := data.Unmarshal(&backends, "backends"); err != nil { + return nil, err + } + for i := 0; i < len(backends); i++ { + if len(backends[i].BackendType) == 0 { + backends[i].BackendType = LB_BACKEND_GUEST + } + if backends[i].Weight < 0 || backends[i].Weight > 256 { + return nil, httperrors.NewInputParameterError("weight %s not support, only support range 0 ~ 256") + } + if backends[i].Port < 1 || backends[i].Port > 65535 { + return nil, httperrors.NewInputParameterError("port %s not support, only support range 1 ~ 65535") + } + if len(backends[i].ID) == 0 { + return nil, httperrors.NewMissingParameterError("Missing backend id") + } + + switch backends[i].BackendType { + case LB_BACKEND_GUEST: + _guest, err := GuestManager.FetchByIdOrName(userCred, backends[i].ID) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("failed to find guest %s", backends[i].ID) + } + return nil, httperrors.NewGeneralError(err) + } + guest := _guest.(*SGuest) + host := guest.GetHost() + if host == nil { + return nil, fmt.Errorf("error getting host of guest %s", guest.Name) + } + backends[i].ZoneId = host.ZoneId + backends[i].HostName = host.Name + backends[i].ID = guest.Id + backends[i].Name = guest.Name + backends[i].ExternalID = guest.ExternalId + + address, err := LoadbalancerBackendManager.GetGuestAddress(guest) + if err != nil { + return nil, err + } + backends[i].Address = address + case LB_BACKEND_HOST: + if !db.IsAdminAllowCreate(userCred, man) { + return nil, httperrors.NewForbiddenError("only sysadmin can specify host as backend") + } + _host, err := HostManager.FetchByIdOrName(userCred, backends[i].ID) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("failed to find host %s", backends[i].ID) + } + return nil, httperrors.NewGeneralError(err) + } + host := _host.(*SHost) + backends[i].ID = host.Id + backends[i].Name = host.Name + backends[i].ExternalID = host.ExternalId + backends[i].Address = host.AccessIp + default: + return nil, httperrors.NewInputParameterError("unexpected backend type %s", backends[i].BackendType) + } + } + } + data.Set("backends", jsonutils.Marshal(backends)) + region := lb.GetRegion() + if region == nil { + return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer %s", lb.Name) + } + return region.GetDriver().ValidateCreateLoadbalancerBackendGroupData(ctx, userCred, data, lb, backends) } func (lbbg *SLoadbalancerBackendGroup) GetLoadbalancer() *SLoadbalancer { - lb, _ := LoadbalancerManager.FetchById(lbbg.LoadbalancerId) + lb, err := LoadbalancerManager.FetchById(lbbg.LoadbalancerId) + if err != nil { + fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + return nil + } return lb.(*SLoadbalancer) } +func (llbg *SLoadbalancerBackendGroup) GetRegion() *SCloudregion { + if loadbalancer := llbg.GetLoadbalancer(); loadbalancer != nil { + return loadbalancer.GetRegion() + } + return nil +} + +func (lbbg *SLoadbalancerBackendGroup) GetIRegion() (cloudprovider.ICloudRegion, error) { + if loadbalancer := lbbg.GetLoadbalancer(); loadbalancer != nil { + return loadbalancer.GetIRegion() + } + return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) +} + func (lbbg *SLoadbalancerBackendGroup) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return false } @@ -101,7 +200,12 @@ func (lbbg *SLoadbalancerBackendGroup) ValidateDeleteCondition(ctx context.Conte lbbgId, n, man.KeywordPlural()) } } - return nil + + region := lbbg.GetRegion() + if region != nil { + return nil + } + return region.GetDriver().ValidateDeleteLoadbalancerBackendGroupCondition(ctx, lbbg) } func (lbbg *SLoadbalancerBackendGroup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { @@ -123,12 +227,30 @@ func (lbbg *SLoadbalancerBackendGroup) GetExtraDetails(ctx context.Context, user return extra, nil } -func (lbbg *SLoadbalancerBackendGroup) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lbbg.DoPendingDelete(ctx, userCred) - lbbg.PreDeleteSubs(ctx, userCred) +func (lbbg *SLoadbalancerBackendGroup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lbbg.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + params := jsonutils.NewDict() + backends, _ := data.Get("backends") + if backends != nil { + params.Add(backends, "backends") + } + lbbg.SetStatus(userCred, LB_CREATING, "") + if err := lbbg.StartLoadBalancerBackendGroupCreateTask(ctx, userCred, params, ""); err != nil { + log.Errorf("Failed to create loadbalancer backendgroup error: %v", err) + } +} + +func (lbbg *SLoadbalancerBackendGroup) StartLoadBalancerBackendGroupCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerLoadbalancerBackendGroupCreateTask", lbbg, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lbbg *SLoadbalancerBackendGroup) PreDeleteSubs(ctx context.Context, userCred mcclient.TokenCredential) { + lbbg.DoPendingDelete(ctx, userCred) subMan := LoadbalancerBackendManager ownerProjId := lbbg.GetOwnerProjectId() @@ -138,6 +260,30 @@ func (lbbg *SLoadbalancerBackendGroup) PreDeleteSubs(ctx context.Context, userCr subMan.PreDeleteSubs(ctx, userCred, q) } +func (lbbg *SLoadbalancerBackendGroup) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lbbg, "purge") +} + +func (lbbg *SLoadbalancerBackendGroup) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lbbg.StartLoadBalancerBackendGroupDeleteTask(ctx, userCred, parasm, "") +} + +func (lbbg *SLoadbalancerBackendGroup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + lbbg.SetStatus(userCred, LB_STATUS_DELETING, "") + return lbbg.StartLoadBalancerBackendGroupDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (lbbg *SLoadbalancerBackendGroup) StartLoadBalancerBackendGroupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerBackendGroupDeleteTask", lbbg, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (lbbg *SLoadbalancerBackendGroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } @@ -218,6 +364,7 @@ func (man *SLoadbalancerBackendGroupManager) SyncLoadbalancerBackendgroups(ctx c func (lbbg *SLoadbalancerBackendGroup) constructFieldsFromCloudBackendgroup(lb *SLoadbalancer, extLoadbalancerBackendgroup cloudprovider.ICloudLoadbalancerBackendGroup) { lbbg.Name = extLoadbalancerBackendgroup.GetName() lbbg.Type = extLoadbalancerBackendgroup.GetType() + lbbg.Status = extLoadbalancerBackendgroup.GetStatus() } func (lbbg *SLoadbalancerBackendGroup) SyncWithCloudLoadbalancerBackendgroup(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, extLoadbalancerBackendgroup cloudprovider.ICloudLoadbalancerBackendGroup, projectId string, projectSync bool) error { @@ -273,7 +420,7 @@ func (man *SLoadbalancerBackendGroupManager) newFromCloudLoadbalancerBackendgrou return lbbg, nil } -func (man *SLoadbalancerBackendGroupManager) InitializeData() error { +func (man *SLoadbalancerBackendGroupManager) initBackendGroupType() error { backendgroups := []SLoadbalancerBackendGroup{} q := man.Query() q = q.Filter(sqlchemy.IsNullOrEmpty(q.Field("type"))) @@ -292,3 +439,33 @@ func (man *SLoadbalancerBackendGroupManager) InitializeData() error { } return nil } + +func (man *SLoadbalancerBackendGroupManager) InitializeData() error { + if err := man.initBackendGroupType(); err != nil { + return err + } + return man.initBackendGroupRegion() +} + +func (manager *SLoadbalancerBackendGroupManager) initBackendGroupRegion() error { + groups := []SLoadbalancerBackendGroup{} + q := manager.Query() + q = q.Filter(sqlchemy.IsNullOrEmpty(q.Field("cloudregion_id"))) + if err := db.FetchModelObjects(manager, q, &groups); err != nil { + return err + } + for i := 0; i < len(groups); i++ { + group := &groups[i] + if lb := group.GetLoadbalancer(); lb != nil && len(lb.CloudregionId) > 0 { + _, err := group.GetModelManager().TableSpec().Update(group, func() error { + group.CloudregionId = lb.CloudregionId + group.ManagerId = lb.ManagerId + return nil + }) + if err != nil { + log.Errorf("failed to update loadbalancer backendgroup %s cloudregion_id", group.Name) + } + } + } + return nil +} diff --git a/pkg/compute/models/loadbalancerbackends.go b/pkg/compute/models/loadbalancerbackends.go index b7f33f709a..291b30f4b0 100644 --- a/pkg/compute/models/loadbalancerbackends.go +++ b/pkg/compute/models/loadbalancerbackends.go @@ -5,12 +5,15 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/util/compare" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -33,7 +36,9 @@ func init() { type SLoadbalancerBackend struct { db.SVirtualResourceBase + SManagedResourceBase + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"optional"` BackendGroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` BackendId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` BackendType string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` @@ -47,7 +52,7 @@ func (man *SLoadbalancerBackendManager) PreDeleteSubs(ctx context.Context, userC subs := []SLoadbalancerBackend{} db.FetchModelObjects(man, q, &subs) for _, sub := range subs { - sub.PreDelete(ctx, userCred) + sub.DoPendingDelete(ctx, userCred) } } @@ -83,47 +88,26 @@ func (man *SLoadbalancerBackendManager) ValidateCreateData(ctx context.Context, } } backendGroup := backendGroupV.Model.(*SLoadbalancerBackendGroup) + lb := backendGroup.GetLoadbalancer() + data.Set("manager_id", jsonutils.NewString(lb.ManagerId)) + data.Set("cloudregion_id", jsonutils.NewString(lb.CloudregionId)) backendType := backendTypeV.Value var baseName string + var backendV *validators.ValidatorModelIdOrName switch backendType { case LB_BACKEND_GUEST: - backendV := validators.NewModelIdOrNameValidator("backend", "server", ownerProjId) + backendV = validators.NewModelIdOrNameValidator("backend", "server", ownerProjId) err := backendV.Validate(data) if err != nil { return nil, err } guest := backendV.Model.(*SGuest) - { - // guest zone must match that of loadbalancer's - host := guest.GetHost() - if host == nil { - return nil, fmt.Errorf("error getting host of guest %s", guest.GetId()) - } - lb := backendGroup.GetLoadbalancer() - if lb == nil { - return nil, fmt.Errorf("error loadbalancer of backend group %s", backendGroup.GetId()) - } - if host.ZoneId != lb.ZoneId { - return nil, fmt.Errorf("zone of host %q (%s) != zone of loadbalancer %q (%s)", - host.Name, host.ZoneId, lb.Name, lb.ZoneId) - } - } - { - // get guest intranet address - // - // NOTE add address hint (cidr) if needed - address, err := man.getGuestAddress(guest) - if err != nil { - return nil, err - } - data.Set("address", jsonutils.NewString(address)) - } baseName = guest.Name case LB_BACKEND_HOST: if !db.IsAdminAllowCreate(userCred, man) { return nil, fmt.Errorf("only sysadmin can specify host as backend") } - backendV := validators.NewModelIdOrNameValidator("backend", "host", userCred.GetProjectId()) + backendV = validators.NewModelIdOrNameValidator("backend", "host", userCred.GetProjectId()) err := backendV.Validate(data) if err != nil { return nil, err @@ -147,16 +131,54 @@ func (man *SLoadbalancerBackendManager) ValidateCreateData(ctx context.Context, // - Use name from input query name := fmt.Sprintf("%s-%s-%s", backendGroup.Name, backendType, baseName) data.Set("name", jsonutils.NewString(name)) - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + region := lb.GetRegion() + if region == nil { + return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer %s", lb.Name) + } + return region.GetDriver().ValidateCreateLoadbalancerBackendData(ctx, userCred, data, backendType, lb, backendGroup, backendV.Model) } func (lbb *SLoadbalancerBackend) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return false } -func (man *SLoadbalancerBackendManager) getGuestAddress(guest *SGuest) (string, error) { - gns := guest.GetNetworks() - if len(gns) == 0 { +func (lbb *SLoadbalancerBackend) GetLoadbalancerBackendGroup() *SLoadbalancerBackendGroup { + backendgroup, err := LoadbalancerBackendGroupManager.FetchById(lbb.BackendGroupId) + if err != nil { + log.Errorf("failed to find backendgroup for backend %s", lbb.Name) + return nil + } + return backendgroup.(*SLoadbalancerBackendGroup) +} + +func (lbb *SLoadbalancerBackend) GetGuest() *SGuest { + guest, err := GuestManager.FetchById(lbb.BackendId) + if err != nil { + return nil + } + return guest.(*SGuest) +} + +func (lbb *SLoadbalancerBackend) GetRegion() *SCloudregion { + if backendgroup := lbb.GetLoadbalancerBackendGroup(); backendgroup != nil { + return backendgroup.GetRegion() + } + return nil +} + +func (lbb *SLoadbalancerBackend) GetIRegion() (cloudprovider.ICloudRegion, error) { + if backendgroup := lbb.GetLoadbalancerBackendGroup(); backendgroup != nil { + return backendgroup.GetIRegion() + } + return nil, fmt.Errorf("failed to find region for backend %s", lbb.Name) +} + +func (man *SLoadbalancerBackendManager) GetGuestAddress(guest *SGuest) (string, error) { + gns, err := guest.GetNetworks("") + if err != nil || len(gns) == 0 { return "", fmt.Errorf("guest %s has no network attached", guest.GetId()) } for _, gn := range gns { @@ -181,14 +203,51 @@ func (lbb *SLoadbalancerBackend) ValidateUpdateData(ctx context.Context, userCre return lbb.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) } -func (lbb *SLoadbalancerBackend) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lbb.DoPendingDelete(ctx, userCred) +func (lbb *SLoadbalancerBackend) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lbb.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + lbb.SetStatus(userCred, LB_CREATING, "") + if err := lbb.StartLoadBalancerBackendCreateTask(ctx, userCred, ""); err != nil { + log.Errorf("Failed to create loadbalancer backend error: %v", err) + } +} + +func (lbb *SLoadbalancerBackend) StartLoadBalancerBackendCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerBackendCreateTask", lbb, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lbb *SLoadbalancerBackend) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } +func (lbb *SLoadbalancerBackend) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lbb, "purge") +} + +func (lbb *SLoadbalancerBackend) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lbb.StartLoadBalancerBackendDeleteTask(ctx, userCred, parasm, "") +} + +func (lbb *SLoadbalancerBackend) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + lbb.SetStatus(userCred, LB_STATUS_DELETING, "") + return lbb.StartLoadBalancerBackendDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (lbb *SLoadbalancerBackend) StartLoadBalancerBackendDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerBackendDeleteTask", lbb, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (man *SLoadbalancerBackendManager) getLoadbalancerBackendsByLoadbalancerBackendgroup(loadbalancerBackendgroup *SLoadbalancerBackendGroup) ([]SLoadbalancerBackend, error) { loadbalancerBackends := []SLoadbalancerBackend{} q := man.Query().Equals("backend_group_id", loadbalancerBackendgroup.Id) @@ -198,6 +257,17 @@ func (man *SLoadbalancerBackendManager) getLoadbalancerBackendsByLoadbalancerBac return loadbalancerBackends, nil } +func (lbb *SLoadbalancerBackend) ValidateDeleteCondition(ctx context.Context) error { + if err := lbb.SVirtualResourceBase.ValidateDeleteCondition(ctx); err != nil { + return err + } + region := lbb.GetRegion() + if region == nil { + return nil + } + return region.GetDriver().ValidateDeleteLoadbalancerBackendCondition(ctx, lbb) +} + func (man *SLoadbalancerBackendManager) SyncLoadbalancerBackends(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, loadbalancerBackendgroup *SLoadbalancerBackendGroup, lbbs []cloudprovider.ICloudLoadbalancerBackend, syncRange *SSyncRange) compare.SyncResult { syncResult := compare.SyncResult{} @@ -271,7 +341,8 @@ func (lbb *SLoadbalancerBackend) constructFieldsFromCloudLoadbalancerBackend(ext return err } guest := instance.(*SGuest) - address, err := LoadbalancerBackendManager.getGuestAddress(guest) + lbb.BackendId = guest.Id + address, err := LoadbalancerBackendManager.GetGuestAddress(guest) if err != nil { return err } @@ -306,3 +377,26 @@ func (man *SLoadbalancerBackendManager) newFromCloudLoadbalancerBackend(ctx cont } return lbb, man.TableSpec().Insert(lbb) } + +func (manager *SLoadbalancerBackendManager) InitializeData() error { + backends := []SLoadbalancerBackend{} + q := manager.Query() + q = q.Filter(sqlchemy.IsNullOrEmpty(q.Field("cloudregion_id"))) + if err := db.FetchModelObjects(manager, q, &backends); err != nil { + return err + } + for i := 0; i < len(backends); i++ { + backend := &backends[i] + if group := backend.GetLoadbalancerBackendGroup(); group != nil && len(group.CloudregionId) > 0 { + _, err := backend.GetModelManager().TableSpec().Update(backend, func() error { + backend.CloudregionId = group.CloudregionId + backend.ManagerId = group.ManagerId + return nil + }) + if err != nil { + log.Errorf("failed to update loadbalancer backend %s cloudregion_id", group.Name) + } + } + } + return nil +} diff --git a/pkg/compute/models/loadbalancercertificates.go b/pkg/compute/models/loadbalancercertificates.go index 9df95b6188..90ee15fc13 100644 --- a/pkg/compute/models/loadbalancercertificates.go +++ b/pkg/compute/models/loadbalancercertificates.go @@ -16,6 +16,7 @@ import ( "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -67,7 +68,7 @@ func (man *SLoadbalancerCertificateManager) PreDeleteSubs(ctx context.Context, u subs := []SLoadbalancerCertificate{} db.FetchModelObjects(man, q, &subs) for _, sub := range subs { - sub.PreDelete(ctx, userCred) + sub.DoPendingDelete(ctx, userCred) } } @@ -125,7 +126,17 @@ func (man *SLoadbalancerCertificateManager) ValidateCreateData(ctx context.Conte if err != nil { return nil, err } - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + + regionV := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", ownerProjId) + regionV.Default("default") + if err := regionV.Validate(data); err != nil { + return nil, err + } + region := regionV.Model.(*SCloudregion) + return region.GetDriver().ValidateCreateLoadbalancerCertificateData(ctx, userCred, data) } func (man *SLoadbalancerCertificateManager) InitializeData() error { @@ -180,7 +191,32 @@ func (lbcert *SLoadbalancerCertificate) ValidateUpdateData(ctx context.Context, if err != nil { return nil, err } - return lbcert.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) + if _, err := lbcert.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil { + return nil, err + } + region := lbcert.GetRegion() + if region == nil { + return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer certificate %s", lbcert.Name) + } + return region.GetDriver().ValidateUpdateLoadbalancerCertificateData(ctx, userCred, data) +} + +func (lbcert *SLoadbalancerCertificate) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lbcert.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + + lbcert.SetStatus(userCred, LB_CREATING, "") + if err := lbcert.StartLoadBalancerCertificateCreateTask(ctx, userCred, ""); err != nil { + log.Errorf("Failed to create loadbalancercertificate error: %v", err) + } +} + +func (lbcert *SLoadbalancerCertificate) StartLoadBalancerCertificateCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerCertificateCreateTask", lbcert, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lbcert *SLoadbalancerCertificate) ValidateDeleteCondition(ctx context.Context) error { @@ -203,11 +239,52 @@ func (lbcert *SLoadbalancerCertificate) ValidateDeleteCondition(ctx context.Cont return nil } -func (lbcert *SLoadbalancerCertificate) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lbcert.DoPendingDelete(ctx, userCred) +func (lbcert *SLoadbalancerCertificate) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil } -func (lbcert *SLoadbalancerCertificate) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { +func (lbcert *SLoadbalancerCertificate) GetRegion() *SCloudregion { + region, err := CloudregionManager.FetchById(lbcert.CloudregionId) + if err != nil { + log.Errorf("failed to find region for loadbalancer certificate %s", lbcert.Name) + return nil + } + return region.(*SCloudregion) +} + +func (lbcert *SLoadbalancerCertificate) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := lbcert.GetDriver() + if err != nil { + return nil, fmt.Errorf("No cloudprovide for lbcert %s: %s", lbcert.Name, err) + } + region := lbcert.GetRegion() + if region == nil { + return nil, fmt.Errorf("failed to find region for lbcert %s", lbcert.Name) + } + return provider.GetIRegionById(region.ExternalId) +} + +func (lbcert *SLoadbalancerCertificate) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lbcert, "purge") +} + +func (lbcert *SLoadbalancerCertificate) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lbcert.StartLoadBalancerCertificateDeleteTask(ctx, userCred, parasm, "") +} + +func (lbcert *SLoadbalancerCertificate) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + lbcert.SetStatus(userCred, LB_STATUS_DELETING, "") + return lbcert.StartLoadBalancerCertificateDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (lbcert *SLoadbalancerCertificate) StartLoadBalancerCertificateDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerCertificateDeleteTask", lbcert, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) return nil } diff --git a/pkg/compute/models/loadbalancerlistenerrules.go b/pkg/compute/models/loadbalancerlistenerrules.go index 2a7685bf69..0dae971fac 100644 --- a/pkg/compute/models/loadbalancerlistenerrules.go +++ b/pkg/compute/models/loadbalancerlistenerrules.go @@ -10,6 +10,7 @@ import ( "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -35,7 +36,9 @@ func init() { type SLoadbalancerListenerRule struct { db.SVirtualResourceBase + SManagedResourceBase + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"optional"` ListenerId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` BackendGroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` @@ -63,7 +66,7 @@ func (man *SLoadbalancerListenerRuleManager) PreDeleteSubs(ctx context.Context, subs := []SLoadbalancerListenerRule{} db.FetchModelObjects(man, q, &subs) for _, sub := range subs { - sub.PreDelete(ctx, userCred) + sub.DoPendingDelete(ctx, userCred) } } @@ -106,6 +109,8 @@ func (man *SLoadbalancerListenerRuleManager) ValidateCreateData(ctx context.Cont } } listener := listenerV.Model.(*SLoadbalancerListener) + data.Set("cloudregion_id", jsonutils.NewString(listener.CloudregionId)) + data.Set("manager_id", jsonutils.NewString(listener.ManagerId)) listenerType := listener.ListenerType if listenerType != LB_LISTENER_TYPE_HTTP && listenerType != LB_LISTENER_TYPE_HTTPS { return nil, fmt.Errorf("listener type must be http/https, got %s", listenerType) @@ -120,7 +125,57 @@ func (man *SLoadbalancerListenerRuleManager) ValidateCreateData(ctx context.Cont if err != nil { return nil, err } - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + region := listener.GetRegion() + if region == nil { + return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer listener %s", listener.Name) + } + + return region.GetDriver().ValidateCreateLoadbalancerListenerRuleData(ctx, userCred, data, backendGroupV.Model) +} + +func (lbr *SLoadbalancerListenerRule) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lbr.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + + lbr.SetStatus(userCred, LB_CREATING, "") + if err := lbr.StartLoadBalancerListenerRuleCreateTask(ctx, userCred, ""); err != nil { + log.Errorf("Failed to create loadbalancer listener rule error: %v", err) + } +} + +func (lbr *SLoadbalancerListenerRule) StartLoadBalancerListenerRuleCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerRuleCreateTask", lbr, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lbr *SLoadbalancerListenerRule) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lbr, "purge") +} + +func (lbr *SLoadbalancerListenerRule) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lbr.StartLoadBalancerListenerRuleDeleteTask(ctx, userCred, parasm, "") +} + +func (lbr *SLoadbalancerListenerRule) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + lbr.SetStatus(userCred, LB_STATUS_DELETING, "") + return lbr.StartLoadBalancerListenerRuleDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (lbr *SLoadbalancerListenerRule) StartLoadBalancerListenerRuleDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerRuleDeleteTask", lbr, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lbr *SLoadbalancerListenerRule) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -176,9 +231,28 @@ func (lbr *SLoadbalancerListenerRule) GetExtraDetails(ctx context.Context, userC return extra, nil } -func (lbr *SLoadbalancerListenerRule) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lbr.SetStatus(userCred, LB_STATUS_DISABLED, "preDelete") - lbr.DoPendingDelete(ctx, userCred) +func (lbr *SLoadbalancerListenerRule) GetLoadbalancerListener() *SLoadbalancerListener { + listener, err := LoadbalancerListenerManager.FetchById(lbr.ListenerId) + if err != nil { + log.Errorf("failed to find listener for loadbalancer listener rule %s", lbr.Name) + return nil + } + return listener.(*SLoadbalancerListener) +} + +func (lbr *SLoadbalancerListenerRule) GetRegion() *SCloudregion { + if listener := lbr.GetLoadbalancerListener(); listener != nil { + return listener.GetRegion() + } + return nil +} + +func (lbr *SLoadbalancerListenerRule) GetLoadbalancerBackendGroup() *SLoadbalancerBackendGroup { + group, err := LoadbalancerBackendGroupManager.FetchById(lbr.BackendGroupId) + if err != nil { + return nil + } + return group.(*SLoadbalancerBackendGroup) } func (lbr *SLoadbalancerListenerRule) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { @@ -293,3 +367,26 @@ func (lbr *SLoadbalancerListenerRule) SyncWithCloudLoadbalancerListenerRule(ctx }) return err } + +func (manager *SLoadbalancerListenerRuleManager) InitializeData() error { + rules := []SLoadbalancerListenerRule{} + q := manager.Query() + q = q.Filter(sqlchemy.IsNullOrEmpty(q.Field("cloudregion_id"))) + if err := db.FetchModelObjects(manager, q, &rules); err != nil { + return err + } + for i := 0; i < len(rules); i++ { + rule := &rules[i] + if listener := rule.GetLoadbalancerListener(); listener != nil && len(listener.CloudregionId) > 0 { + _, err := listener.GetModelManager().TableSpec().Update(rule, func() error { + rule.CloudregionId = listener.CloudregionId + rule.ManagerId = listener.ManagerId + return nil + }) + if err != nil { + log.Errorf("failed to update loadbalancer listener rule %s cloudregion_id", rule.Name) + } + } + } + return nil +} diff --git a/pkg/compute/models/loadbalancerlisteners.go b/pkg/compute/models/loadbalancerlisteners.go index 7d93427d76..6dc26364f8 100644 --- a/pkg/compute/models/loadbalancerlisteners.go +++ b/pkg/compute/models/loadbalancerlisteners.go @@ -8,10 +8,12 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -68,11 +70,14 @@ type SLoadbalancerHTTPSListener struct { type SLoadbalancerListener struct { db.SVirtualResourceBase + SManagedResourceBase - LoadbalancerId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` - ListenerType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` - ListenerPort int `nullable:"false" list:"user" create:"required"` - BackendGroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"optional"` + LoadbalancerId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` + ListenerType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + ListenerPort int `nullable:"false" list:"user" create:"required"` + BackendGroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + BackendServerPort int `nullable:"false" get:"user" list:"user" default:"0" create:"optional"` Scheduler string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required" update:"user"` @@ -134,7 +139,7 @@ func (man *SLoadbalancerListenerManager) PreDeleteSubs(ctx context.Context, user subs := []SLoadbalancerListener{} db.FetchModelObjects(man, q, &subs) for _, sub := range subs { - sub.PreDelete(ctx, userCred) + sub.DoPendingDelete(ctx, userCred) } } @@ -201,6 +206,8 @@ func (man *SLoadbalancerListenerManager) ValidateCreateData(ctx context.Context, } } lb := lbV.Model.(*SLoadbalancer) + data.Set("manager_id", jsonutils.NewString(lb.ManagerId)) + data.Set("cloudregion_id", jsonutils.NewString(lb.CloudregionId)) listenerPort := listenerPortV.Value listenerType := listenerTypeV.Value { @@ -230,6 +237,10 @@ func (man *SLoadbalancerListenerManager) ValidateCreateData(ctx context.Context, return nil, err } } + cert := certV.Model.(*SLoadbalancerCertificate) + if cert.CloudregionId != lb.CloudregionId { + return nil, httperrors.NewInputParameterError("certificate %s(%s) and lb %s(%s) are not in the same region", cert.Name, cert.Id, lb.Name, lb.Id) + } } } { @@ -239,8 +250,8 @@ func (man *SLoadbalancerListenerManager) ValidateCreateData(ctx context.Context, "health_check": validators.NewStringChoicesValidator("health_check", LB_BOOL_VALUES).Default(LB_BOOL_ON), "health_check_type": checkTypeV, - "health_check_domain": validators.NewDomainNameValidator("domain").AllowEmpty(true).Default(""), - "health_check_path": validators.NewURLPathValidator("path").Default(""), + "health_check_domain": validators.NewDomainNameValidator("health_check_domain").AllowEmpty(true).Default(""), + "health_check_path": validators.NewURLPathValidator("health_check_path").Default(""), "health_check_http_code": validators.NewStringMultiChoicesValidator("health_check_http_code", LB_HEALTH_CHECK_HTTP_CODES).Sep(",").Default(LB_HEALTH_CHECK_HTTP_CODE_DEFAULT), "health_check_rise": validators.NewRangeValidator("health_check_rise", 1, 1000).Default(3), @@ -257,7 +268,14 @@ func (man *SLoadbalancerListenerManager) ValidateCreateData(ctx context.Context, if err := man.validateAcl(aclStatusV, aclTypeV, aclV, data); err != nil { return nil, err } - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + region := lb.GetRegion() + if region == nil { + return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer %s", lb.Name) + } + return region.GetDriver().ValidateCreateLoadbalancerListenerData(ctx, userCred, data, backendGroupV.Model) } func (man *SLoadbalancerListenerManager) checkTypeV(listenerType string) validators.IValidator { @@ -291,6 +309,55 @@ func (lblis *SLoadbalancerListener) AllowPerformStatus(ctx context.Context, user return lblis.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, lblis, "status") } +func (lblis *SLoadbalancerListener) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if _, err := lblis.SVirtualResourceBase.PerformStatus(ctx, userCred, query, data); err != nil { + return nil, err + } + if lblis.Status == LB_STATUS_ENABLED { + return nil, lblis.StartLoadBalancerListenerStartTask(ctx, userCred, "") + } + return nil, lblis.StartLoadBalancerListenerStopTask(ctx, userCred, "") +} + +func (lblis *SLoadbalancerListener) StartLoadBalancerListenerStartTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerStartTask", lblis, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lblis *SLoadbalancerListener) StartLoadBalancerListenerStopTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerStopTask", lblis, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lblis *SLoadbalancerListener) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lblis, "syncstatus") +} + +func (lblis *SLoadbalancerListener) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + if utils.IsInStringArray(lblis.Status, []string{LB_STATUS_ENABLED, LB_STATUS_DISABLED}) { + params.Add(jsonutils.NewString(lblis.Status), "origin_status") + } + return nil, lblis.StartLoadBalancerListenerSyncstatusTask(ctx, userCred, params, "") +} + +func (lblis *SLoadbalancerListener) StartLoadBalancerListenerSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerSyncstatusTask", lblis, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (lblis *SLoadbalancerListener) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { ownerProjId := lblis.GetOwnerProjectId() backendGroupV := validators.NewModelIdOrNameValidator("backend_group", "loadbalancerbackendgroup", ownerProjId) @@ -326,8 +393,8 @@ func (lblis *SLoadbalancerListener) ValidateUpdateData(ctx context.Context, user "health_check": validators.NewStringChoicesValidator("health_check", LB_BOOL_VALUES), "health_check_type": LoadbalancerListenerManager.checkTypeV(lblis.ListenerType), - "health_check_domain": validators.NewDomainNameValidator("domain").AllowEmpty(true), - "health_check_path": validators.NewURLPathValidator("path"), + "health_check_domain": validators.NewDomainNameValidator("health_check_domain").AllowEmpty(true), + "health_check_path": validators.NewURLPathValidator("health_check_path"), "health_check_http_code": validators.NewStringMultiChoicesValidator("health_check_http_code", LB_HEALTH_CHECK_HTTP_CODES).Sep(","), "health_check_rise": validators.NewRangeValidator("health_check_rise", 1, 1000), @@ -360,7 +427,35 @@ func (lblis *SLoadbalancerListener) ValidateUpdateData(ctx context.Context, user backendGroup.Name, backendGroup.Id, backendGroup.LoadbalancerId, lblis.LoadbalancerId) } } - return lblis.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) + if _, err := lblis.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil { + return nil, err + } + + region := lblis.GetRegion() + if region == nil { + return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer listener %s", lblis.Name) + } + + return region.GetDriver().ValidateUpdateLoadbalancerListenerData(ctx, userCred, data, backendGroupV.Model) +} + +func (lblis *SLoadbalancerListener) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lblis.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data) + lblis.StartLoadBalancerListenerSyncTask(ctx, userCred, "") +} + +func (lblis *SLoadbalancerListener) StartLoadBalancerListenerSyncTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + if utils.IsInStringArray(lblis.Status, []string{LB_STATUS_ENABLED, LB_STATUS_DISABLED}) { + params.Add(jsonutils.NewString(lblis.Status), "origin_status") + } + lblis.SetStatus(userCred, LB_SYNC_CONF, "") + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerSyncTask", lblis, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lblis *SLoadbalancerListener) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { @@ -394,10 +489,54 @@ func (lblis *SLoadbalancerListener) GetExtraDetails(ctx context.Context, userCre return extra, nil } -func (lblis *SLoadbalancerListener) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lblis.SetStatus(userCred, LB_STATUS_DISABLED, "preDelete") - lblis.DoPendingDelete(ctx, userCred) - lblis.PreDeleteSubs(ctx, userCred) +func (lblis *SLoadbalancerListener) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + lblis.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + + lblis.SetStatus(userCred, LB_CREATING, "") + if err := lblis.StartLoadBalancerListenerCreateTask(ctx, userCred, ""); err != nil { + log.Errorf("Failed to create loadbalancer listener error: %v", err) + } +} + +func (lblis *SLoadbalancerListener) StartLoadBalancerListenerCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerCreateTask", lblis, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lblis *SLoadbalancerListener) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lblis, "purge") +} + +func (lblis *SLoadbalancerListener) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lblis.StartLoadBalancerListenerDeleteTask(ctx, userCred, parasm, "") +} + +func (lblis *SLoadbalancerListener) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lblis, "sync") +} + +func (lblis *SLoadbalancerListener) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, lblis.StartLoadBalancerListenerSyncTask(ctx, userCred, "") +} + +func (lblis *SLoadbalancerListener) StartLoadBalancerListenerDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerListenerDeleteTask", lblis, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lblis *SLoadbalancerListener) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + lblis.SetStatus(userCred, LB_STATUS_DELETING, "") + return lblis.StartLoadBalancerListenerDeleteTask(ctx, userCred, jsonutils.NewDict(), "") } func (lblis *SLoadbalancerListener) PreDeleteSubs(ctx context.Context, userCred mcclient.TokenCredential) { @@ -408,12 +547,110 @@ func (lblis *SLoadbalancerListener) PreDeleteSubs(ctx context.Context, userCred defer lockman.ReleaseClass(ctx, subMan, ownerProjId) q := subMan.Query().Equals("listener_id", lblis.Id) subMan.PreDeleteSubs(ctx, userCred, q) + lblis.DoPendingDelete(ctx, userCred) } func (lblis *SLoadbalancerListener) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } +func (lblis *SLoadbalancerListener) GetLoadbalancerListenerParams() (*cloudprovider.SLoadbalancerListener, error) { + listener := &cloudprovider.SLoadbalancerListener{ + Name: lblis.Name, + Description: lblis.Description, + ListenerType: lblis.ListenerType, + ListenerPort: lblis.ListenerPort, + Scheduler: lblis.Scheduler, + EnableHTTP2: lblis.EnableHttp2, + Bandwidth: 0, + EstablishedTimeout: lblis.BackendConnectTimeout, + + HealthCheck: lblis.HealthCheck, + HealthCheckTimeout: lblis.HealthCheckTimeout, + HealthCheckDomain: lblis.HealthCheckDomain, + HealthCheckHttpCode: lblis.HealthCheckHttpCode, + HealthCheckURI: lblis.HealthCheckURI, + HealthCheckInterval: lblis.HealthCheckInterval, + + HealthCheckRise: lblis.HealthCheckRise, + HealthCheckFail: lblis.HealthCheckFall, + + StickySession: lblis.StickySession, + StickySessionType: lblis.StickySessionType, + StickySessionCookie: lblis.StickySessionCookie, + StickySessionCookieTimeout: lblis.StickySessionCookieTimeout, + + BackendServerPort: lblis.BackendServerPort, + XForwardedFor: lblis.XForwardedFor, + TLSCipherPolicy: lblis.TLSCipherPolicy, + Gzip: lblis.Gzip, + } + if acl := lblis.GetLoadbalancerAcl(); acl != nil { + listener.AccessControlListID = acl.ExternalId + listener.AccessControlListType = lblis.AclType + listener.AccessControlListStatus = lblis.AclStatus + } + if certificate := lblis.GetLoadbalancerCertificate(); certificate != nil && lblis.ListenerType == LB_LISTENER_TYPE_HTTPS { + listener.CertificateID = certificate.ExternalId + } + + if backendgroup := lblis.GetLoadbalancerBackendGroup(); backendgroup != nil { + listener.BackendGroupID = backendgroup.ExternalId + listener.BackendGroupType = backendgroup.Type + } + return listener, nil +} + +func (lblis *SLoadbalancerListener) GetLoadbalancerCertificate() *SLoadbalancerCertificate { + if len(lblis.CertificateId) == 0 { + return nil + } + certificate, err := LoadbalancerCertificateManager.FetchById(lblis.CertificateId) + if err != nil { + return nil + } + return certificate.(*SLoadbalancerCertificate) +} + +func (lblis *SLoadbalancerListener) GetLoadbalancerAcl() *SLoadbalancerAcl { + acl, err := LoadbalancerAclManager.FetchById(lblis.AclId) + if err != nil { + return nil + } + return acl.(*SLoadbalancerAcl) +} + +func (lblis *SLoadbalancerListener) GetLoadbalancerBackendGroup() *SLoadbalancerBackendGroup { + group, err := LoadbalancerBackendGroupManager.FetchById(lblis.BackendGroupId) + if err != nil { + return nil + } + return group.(*SLoadbalancerBackendGroup) +} + +func (lblis *SLoadbalancerListener) GetLoadbalancer() *SLoadbalancer { + loadbalancer, err := LoadbalancerManager.FetchById(lblis.LoadbalancerId) + if err != nil { + log.Errorf("failed to find loadbalancer for loadbalancer listener %s", lblis.Name) + return nil + } + return loadbalancer.(*SLoadbalancer) +} + +func (lblis *SLoadbalancerListener) GetRegion() *SCloudregion { + if loadbalancer := lblis.GetLoadbalancer(); loadbalancer != nil { + return loadbalancer.GetRegion() + } + return nil +} + +func (lblis *SLoadbalancerListener) GetIRegion() (cloudprovider.ICloudRegion, error) { + if loadbalancer := lblis.GetLoadbalancer(); loadbalancer != nil { + return loadbalancer.GetIRegion() + } + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) +} + func (man *SLoadbalancerListenerManager) getLoadbalancerListenersByLoadbalancer(lb *SLoadbalancer) ([]SLoadbalancerListener, error) { listeners := []SLoadbalancerListener{} q := man.Query().Equals("loadbalancer_id", lb.Id) @@ -505,6 +742,7 @@ func (lblis *SLoadbalancerListener) constructFieldsFromCloudListener(lb *SLoadba lblis.HealthCheckType = extListener.GetHealthCheckType() lblis.HealthCheckTimeout = extListener.GetHealthCheckTimeout() lblis.HealthCheckInterval = extListener.GetHealthCheckInterval() + lblis.BackendServerPort = extListener.GetBackendServerPort() switch lblis.ListenerType { case LB_LISTENER_TYPE_HTTPS: @@ -557,3 +795,26 @@ func (man *SLoadbalancerListenerManager) newFromCloudLoadbalancerListener(ctx co } return lblis, man.TableSpec().Insert(lblis) } + +func (manager *SLoadbalancerListenerManager) InitializeData() error { + listeners := []SLoadbalancerListener{} + q := manager.Query() + q = q.Filter(sqlchemy.IsNullOrEmpty(q.Field("cloudregion_id"))) + if err := db.FetchModelObjects(manager, q, &listeners); err != nil { + return err + } + for i := 0; i < len(listeners); i++ { + listener := &listeners[i] + if lb := listener.GetLoadbalancer(); lb != nil && len(lb.CloudregionId) > 0 { + _, err := listener.GetModelManager().TableSpec().Update(listener, func() error { + listener.CloudregionId = lb.CloudregionId + listener.ManagerId = lb.ManagerId + return nil + }) + if err != nil { + log.Errorf("failed to update loadbalancer listener %s cloudregion_id", listener.Name) + } + } + } + return nil +} diff --git a/pkg/compute/models/loadbalancernetworks.go b/pkg/compute/models/loadbalancernetworks.go index 30e533c467..28244397ce 100644 --- a/pkg/compute/models/loadbalancernetworks.go +++ b/pkg/compute/models/loadbalancernetworks.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/regutils" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -35,8 +37,8 @@ func init() { type SLoadbalancerNetwork struct { db.SVirtualJointResourceBase - LoadbalancerId string `width:"36" charset:"ascii" nullable:"false" key_index:"true" list:"admin"` - NetworkId string `width:"36" charset:"ascii" nullable:"false" key_index:"true" list:"admin"` + LoadbalancerId string `width:"36" charset:"ascii" nullable:"false" list:"admin"` + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"admin"` IpAddr string `width:"16" charset:"ascii" list:"admin"` } @@ -49,10 +51,10 @@ func (ln *SLoadbalancerNetwork) Network() *SNetwork { } type SLoadbalancerNetworkRequestData struct { - loadbalancer *SLoadbalancer - networkId string + Loadbalancer *SLoadbalancer + NetworkId string reserved bool // allocate from reserved - address string // the address user intends to use + Address string // the address user intends to use strategy IPAddlocationDirection // allocate bottom up, top down, randomly } @@ -66,13 +68,13 @@ func (m *SLoadbalancernetworkManager) NewLoadbalancerNetwork(ctx context.Context if networkMan == nil { return nil, fmt.Errorf("failed getting network manager") } - im, err := networkMan.FetchById(req.networkId) + im, err := networkMan.FetchById(req.NetworkId) if err != nil { return nil, err } network := im.(*SNetwork) ln := &SLoadbalancerNetwork{ - LoadbalancerId: req.loadbalancer.Id, + LoadbalancerId: req.Loadbalancer.Id, NetworkId: network.Id, } ln.SetModelManager(m) @@ -82,7 +84,7 @@ func (m *SLoadbalancernetworkManager) NewLoadbalancerNetwork(ctx context.Context usedMap := network.GetUsedAddresses() recentReclaimed := map[string]bool{} ipAddr, err := network.GetFreeIP(ctx, userCred, - usedMap, recentReclaimed, req.address, req.strategy, req.reserved) + usedMap, recentReclaimed, req.Address, req.strategy, req.reserved) if err != nil { return nil, err } @@ -122,6 +124,46 @@ func (m *SLoadbalancernetworkManager) DeleteLoadbalancerNetwork(ctx context.Cont return nil } +func (m *SLoadbalancernetworkManager) SyncLoadbalancerNetwork(ctx context.Context, userCred mcclient.TokenCredential, req *SLoadbalancerNetworkRequestData) error { + _network, err := NetworkManager.FetchById(req.NetworkId) + if err != nil { + return err + } + network := _network.(*SNetwork) + ip, err := netutils.NewIPV4Addr(req.Address) + if err != nil { + return err + } + if !network.isAddressInRange(ip) { + return fmt.Errorf("address %s is not in the range of network %s(%s)", req.Address, network.Id, network.Name) + } + q := m.Query().Equals("loadbalancer_id", req.Loadbalancer.Id).Equals("network_id", req.NetworkId) + lns := []SLoadbalancerNetwork{} + if err := db.FetchModelObjects(m, q, &lns); err != nil { + return err + } + if len(lns) == 0 { + ln := &SLoadbalancerNetwork{LoadbalancerId: req.Loadbalancer.Id, NetworkId: req.NetworkId, IpAddr: req.Address} + return m.TableSpec().Insert(ln) + } + for i := 0; i < len(lns); i++ { + if i == 0 { + if lns[i].IpAddr != req.Address { + _, err := lns[i].GetModelManager().TableSpec().Update(&lns[i], func() error { + lns[i].IpAddr = req.Address + return nil + }) + if err != nil { + log.Errorf("update loadbalancer network ipaddr %s error: %v", lns[i].LoadbalancerId, err) + } + } + } else { + lns[i].Delete(ctx, userCred) + } + } + return nil +} + func (lbNetwork *SLoadbalancerNetwork) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return db.DeleteModel(ctx, userCred, lbNetwork) } diff --git a/pkg/compute/models/loadbalancers.go b/pkg/compute/models/loadbalancers.go index 8052c24d6f..be072ab187 100644 --- a/pkg/compute/models/loadbalancers.go +++ b/pkg/compute/models/loadbalancers.go @@ -12,6 +12,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -35,6 +36,11 @@ func init() { } } +const ( + LB_CHARGE_TYPE_BY_TRAFFIC = "traffic" + LB_CHARGE_TYPE_BY_BANDWIDTH = "bandwidth" +) + // TODO build errors on pkg/httperrors/errors.go // NewGetManagerError // NewMissingArgumentError @@ -49,15 +55,18 @@ type SLoadbalancer struct { db.SVirtualResourceBase SManagedResourceBase - Address string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional"` + Address string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"optional"` AddressType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional"` NetworkType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional"` - NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` ZoneId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"admin" default:"default" create:"optional"` - BackendGroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" update:"user" update:"user"` + ChargeType string `list:"user" get:"user" create:"optional"` + LoadbalancerSpec string `list:"user" get:"user" create:"optional"` + + BackendGroupId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" update:"user"` } func (man *SLoadbalancerManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { @@ -79,13 +88,26 @@ func (man *SLoadbalancerManager) ListItemFilter(ctx context.Context, q *sqlchemy func (man *SLoadbalancerManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { networkV := validators.NewModelIdOrNameValidator("network", "network", ownerProjId) + addressType, _ := data.GetString("address_type") + zoneV := validators.NewModelIdOrNameValidator("zone", "zone", "") + managerIdV := validators.NewModelIdOrNameValidator("manager_id", "cloudprovider", "") + if addressType == LB_ADDR_TYPE_INTERNET { + networkV.Optional(true) + } else { + zoneV.Optional(true) + managerIdV.Optional(true) + } addressV := validators.NewIPv4AddrValidator("address") + addressTypeV := validators.NewStringChoicesValidator("address_type", LB_ADDR_TYPES) { keyV := map[string]validators.IValidator{ "status": validators.NewStringChoicesValidator("status", LB_STATUS_SPEC).Default(LB_STATUS_ENABLED), - "address": addressV.Optional(true), - "network": networkV, + "address": addressV.Optional(true), + "address_type": addressTypeV.Default(LB_ADDR_TYPE_INTRANET), + "network": networkV, + "zone": zoneV, + "manager_id": managerIdV, } for _, v := range keyV { if err := v.Validate(data); err != nil { @@ -93,7 +115,8 @@ func (man *SLoadbalancerManager) ValidateCreateData(ctx context.Context, userCre } } } - { + var region *SCloudregion + if addressTypeV.Value == LB_ADDR_TYPE_INTRANET { network := networkV.Model.(*SNetwork) if ipAddr := addressV.IP; ipAddr != nil { ipS := ipAddr.String() @@ -117,12 +140,20 @@ func (man *SLoadbalancerManager) ValidateCreateData(ctx context.Context, userCre if wire == nil { return nil, fmt.Errorf("getting wire failed") } + vpc := wire.getVpc() + if vpc == nil { + return nil, fmt.Errorf("getting vpc failed") + } + data.Set("vpc_id", jsonutils.NewString(vpc.Id)) + if len(vpc.ManagerId) > 0 { + data.Set("manager_id", jsonutils.NewString(vpc.ManagerId)) + } zone := wire.GetZone() if zone == nil { return nil, fmt.Errorf("getting zone failed") } data.Set("zone_id", jsonutils.NewString(zone.GetId())) - region := zone.GetRegion() + region = zone.GetRegion() if region == nil { return nil, fmt.Errorf("getting region failed") } @@ -130,39 +161,215 @@ func (man *SLoadbalancerManager) ValidateCreateData(ctx context.Context, userCre // TODO validate network is of classic type data.Set("network_type", jsonutils.NewString(LB_NETWORK_TYPE_CLASSIC)) data.Set("address_type", jsonutils.NewString(LB_ADDR_TYPE_INTRANET)) + } else { + zone := zoneV.Model.(*SZone) + region = zone.GetRegion() + if region == nil { + return nil, fmt.Errorf("getting region failed") + } + // 公网 lb 实例和vpc、network无关联 + data.Set("vpc_id", jsonutils.NewString("")) + data.Set("address", jsonutils.NewString("")) + data.Set("network_id", jsonutils.NewString("")) + data.Set("cloudregion_id", jsonutils.NewString(region.GetId())) + data.Set("network_type", jsonutils.NewString(LB_NETWORK_TYPE_VPC)) + data.Set("address_type", jsonutils.NewString(LB_ADDR_TYPE_INTERNET)) } - return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if _, err := man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data); err != nil { + return nil, err + } + return region.GetDriver().ValidateCreateLoadbalancerData(ctx, userCred, data) } func (lb *SLoadbalancer) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return lb.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, lb, "status") } +func (lb *SLoadbalancer) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if _, err := lb.SVirtualResourceBase.PerformStatus(ctx, userCred, query, data); err != nil { + return nil, err + } + if lb.Status == LB_STATUS_ENABLED { + return nil, lb.StartLoadBalancerStartTask(ctx, userCred, "") + } + return nil, lb.StartLoadBalancerStopTask(ctx, userCred, "") +} + +func (lb *SLoadbalancer) StartLoadBalancerStartTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerStartTask", lb, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lb *SLoadbalancer) StartLoadBalancerStopTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerStopTask", lb, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lb *SLoadbalancer) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lb, "syncstatus") +} + +func (lb *SLoadbalancer) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, lb.StartLoadBalancerSyncstatusTask(ctx, userCred, "") +} + +func (lb *SLoadbalancer) StartLoadBalancerSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(lb.Status), "origin_status") + lb.SetStatus(userCred, LB_SYNC_STATUS, "") + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerSyncstatusTask", lb, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (lb *SLoadbalancer) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { lb.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) // NOTE lb.Id will only be available after BeforeInsert happens // NOTE this means lb.UpdateVersion will be 0, then 1 after creation // NOTE need ways to notify error - LoadbalancerManager.TableSpec().Update(lb, func() error { - if lb.AddressType == LB_ADDR_TYPE_INTRANET { - // TODO support use reserved ip address - // TODO prefer ip address from server_type loadbalancer? - req := &SLoadbalancerNetworkRequestData{ - loadbalancer: lb, - networkId: lb.NetworkId, - address: lb.Address, - } - // NOTE the small window when agents can see the ephemeral address - ln, err := LoadbalancernetworkManager.NewLoadbalancerNetwork(ctx, userCred, req) - if err != nil { - log.Errorf("allocating loadbalancer network failed: %v, req: %#v", err, req) - lb.Address = "" - } else { - lb.Address = ln.IpAddr - } - } + + lb.SetStatus(userCred, LB_CREATING, "") + if err := lb.StartLoadBalancerCreateTask(ctx, userCred, ""); err != nil { + log.Errorf("Failed to create loadbalancer error: %v", err) + } +} + +func (lb *SLoadbalancer) GetCloudprovider() *SCloudprovider { + cloudprovider, err := CloudproviderManager.FetchById(lb.ManagerId) + if err != nil { return nil - }) + } + return cloudprovider.(*SCloudprovider) +} + +func (lb *SLoadbalancer) GetRegion() *SCloudregion { + region, err := CloudregionManager.FetchById(lb.CloudregionId) + if err != nil { + log.Errorf("failed to find region for loadbalancer %s", lb.Name) + return nil + } + return region.(*SCloudregion) +} + +func (lb *SLoadbalancer) GetZone() *SZone { + zone, err := ZoneManager.FetchById(lb.ZoneId) + if err != nil { + return nil + } + return zone.(*SZone) +} + +func (lb *SLoadbalancer) GetVpc() *SVpc { + vpc, err := VpcManager.FetchById(lb.VpcId) + if err != nil { + return nil + } + return vpc.(*SVpc) +} + +func (lb *SLoadbalancer) GetNetwork() *SNetwork { + network, err := NetworkManager.FetchById(lb.NetworkId) + if err != nil { + return nil + } + return network.(*SNetwork) +} + +func (lb *SLoadbalancer) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := lb.GetDriver() + if err != nil { + return nil, fmt.Errorf("No cloudprovide for lb %s: %s", lb.Name, err) + } + region := lb.GetRegion() + if region == nil { + return nil, fmt.Errorf("failed to find region for lb %s", lb.Name) + } + return provider.GetIRegionById(region.ExternalId) +} + +func (lb *SLoadbalancer) GetCreateLoadbalancerParams(iRegion cloudprovider.ICloudRegion) (*cloudprovider.SLoadbalancer, error) { + params := &cloudprovider.SLoadbalancer{ + Name: lb.Name, + Address: lb.Address, + AddressType: lb.AddressType, + ChargeType: lb.ChargeType, + LoadbalancerSpec: lb.LoadbalancerSpec, + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + if len(lb.ZoneId) > 0 { + zone := lb.GetZone() + if zone == nil { + return nil, fmt.Errorf("failed to find zone for lb %s", lb.Name) + } + iZone, err := iRegion.GetIZoneById(zone.ExternalId) + if err != nil { + return nil, err + } + params.ZoneID = iZone.GetId() + } + if lb.AddressType == LB_ADDR_TYPE_INTRANET { + vpc := lb.GetVpc() + if vpc == nil { + return nil, fmt.Errorf("failed to find vpc for lb %s", lb.Name) + } + iVpc, err := iRegion.GetIVpcById(vpc.ExternalId) + if err != nil { + return nil, err + } + params.VpcID = iVpc.GetId() + network := lb.GetNetwork() + if network == nil { + return nil, fmt.Errorf("failed to find network for lb %s", lb.Name) + } + iNetwork, err := network.GetINetwork() + if err != nil { + return nil, err + } + params.NetworkID = iNetwork.GetId() + } + return params, nil +} + +func (lb *SLoadbalancer) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, lb, "purge") +} + +func (lb *SLoadbalancer) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + parasm := jsonutils.NewDict() + parasm.Add(jsonutils.JSONTrue, "purge") + return nil, lb.StartLoadBalancerDeleteTask(ctx, userCred, parasm, "") +} + +func (lb *SLoadbalancer) StartLoadBalancerDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerDeleteTask", lb, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (lb *SLoadbalancer) StartLoadBalancerCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "LoadbalancerCreateTask", lb, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } func (lb *SLoadbalancer) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { @@ -200,23 +407,17 @@ func (lb *SLoadbalancer) GetExtraDetails(ctx context.Context, userCred mcclient. } func (lb *SLoadbalancer) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - if len(lb.Address) > 0 { - // TODO reserve support + lb.SetStatus(userCred, LB_STATUS_DELETING, "") + return lb.StartLoadBalancerDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (lb *SLoadbalancer) PendingDelete(ctx context.Context, userCred mcclient.TokenCredential) { + if len(lb.NetworkId) > 0 { req := &SLoadbalancerNetworkDeleteData{ loadbalancer: lb, } - err := LoadbalancernetworkManager.DeleteLoadbalancerNetwork(ctx, userCred, req) - if err != nil { - return err - } - lb.Address = "" + LoadbalancernetworkManager.DeleteLoadbalancerNetwork(ctx, userCred, req) } - // TODO How about mark pending delete and return - return nil -} - -func (lb *SLoadbalancer) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { - lb.SetStatus(userCred, LB_STATUS_DISABLED, "preDelete") lb.DoPendingDelete(ctx, userCred) lb.PreDeleteSubs(ctx, userCred) } @@ -326,6 +527,8 @@ func (man *SLoadbalancerManager) newFromCloudLoadbalancer(ctx context.Context, u lb.NetworkType = extLb.GetNetworkType() lb.Name = extLb.GetName() lb.Status = extLb.GetStatus() + lb.LoadbalancerSpec = extLb.GetLoadbalancerSpec() + lb.ChargeType = extLb.GetChargeType() lb.ExternalId = extLb.GetGlobalId() if networkId := extLb.GetNetworkId(); len(networkId) > 0 { if network, err := NetworkManager.FetchByExternalId(networkId); err == nil && network != nil { @@ -352,21 +555,38 @@ func (man *SLoadbalancerManager) newFromCloudLoadbalancer(ctx context.Context, u log.Errorf("newFromCloudRegion fail %s", err) return nil, err } + lb.syncLoadbalancerNetwork(ctx, userCred) return &lb, nil } +func (lb *SLoadbalancer) syncLoadbalancerNetwork(ctx context.Context, userCred mcclient.TokenCredential) { + if len(lb.NetworkId) > 0 { + lbNetReq := &SLoadbalancerNetworkRequestData{ + Loadbalancer: lb, + NetworkId: lb.NetworkId, + Address: lb.Address, + } + err := LoadbalancernetworkManager.SyncLoadbalancerNetwork(ctx, userCred, lbNetReq) + if err != nil { + log.Errorf("failed to create loadbalancer network: %v", err) + } + } +} + func (lb *SLoadbalancer) SyncWithCloudLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, extLb cloudprovider.ICloudLoadbalancer, projectId string, projectSync bool) error { _, err := lb.GetModelManager().TableSpec().Update(lb, func() error { lb.Address = extLb.GetAddress() lb.Status = extLb.GetStatus() lb.Name = extLb.GetName() + lb.LoadbalancerSpec = extLb.GetLoadbalancerSpec() + lb.ChargeType = extLb.GetChargeType() if projectSync && len(projectId) > 0 { lb.ProjectId = projectId } - return nil }) + lb.syncLoadbalancerNetwork(ctx, userCred) return err } diff --git a/pkg/compute/models/netinterfaces.go b/pkg/compute/models/netinterfaces.go index a61edd6aea..71aa440ba2 100644 --- a/pkg/compute/models/netinterfaces.go +++ b/pkg/compute/models/netinterfaces.go @@ -219,7 +219,7 @@ func (self *SNetInterface) Remove(ctx context.Context, userCred mcclient.TokenCr host := self.GetBaremetal() wire := self.GetWire() if host != nil && wire != nil { - hw, err := HostwireManager.FetchByIds(host.Id, wire.Id) + hw, err := db.FetchJointByIds(HostwireManager, host.Id, wire.Id, nil) if err != nil { log.Errorf("NetInterface remove HostwireManager.FetchByIds error %s", err) return err diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 052c573e93..1131ae06e3 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -13,7 +14,6 @@ import ( "yunion.io/x/pkg/util/fileutils" "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/regutils" - "yunion.io/x/pkg/util/sets" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -32,9 +32,11 @@ const ( // # DEFAULT_BANDWIDTH = options.default_bandwidth MAX_BANDWIDTH = 100000 - SERVER_TYPE_GUEST = "guest" - SERVER_TYPE_BAREMETAL = "baremetal" - SERVER_TYPE_CONTAINER = "container" + NETWORK_TYPE_GUEST = "guest" + NETWORK_TYPE_BAREMETAL = "baremetal" + NETWORK_TYPE_CONTAINER = "container" + NETWORK_TYPE_PXE = "pxe" + NETWORK_TYPE_IPMI = "ipmi" STATIC_ALLOC = "static" @@ -53,6 +55,16 @@ const ( NETWORK_STATUS_DELETE_FAILED = "delete_failed" ) +var ( + ALL_NETWORK_TYPES = []string{ + NETWORK_TYPE_GUEST, + NETWORK_TYPE_BAREMETAL, + NETWORK_TYPE_CONTAINER, + NETWORK_TYPE_PXE, + NETWORK_TYPE_IPMI, + } +) + type IPAddlocationDirection string const ( @@ -108,7 +120,7 @@ type SNetwork struct { // IsChanged = Column(Boolean, nullable=False, default=False) - ServerType string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True) + ServerType string `width:"16" charset:"ascii" default:"guest" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True) AllocPolicy string `width:"16" charset:"ascii" nullable:"true" get:"user" update:"user" create:"optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True) @@ -671,6 +683,8 @@ type SNetworkConfig struct { BwLimit int Vip bool Reserved bool + Ifname string + NetType string } func parseNetworkInfo(userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SNetworkConfig, error) { @@ -720,12 +734,23 @@ func parseNetworkInfo(userCred mcclient.TokenCredential, info jsonutils.JSONObje netConfig.BwLimit = bw } else if p == "[vip]" { netConfig.Vip = true + } else if utils.IsInStringArray(p, ALL_NETWORK_TYPES) { + netConfig.NetType = p } else { netObj, err := NetworkManager.FetchByIdOrName(userCred, p) if err != nil { - return nil, err + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(NetworkManager.Keyword(), p) + } else { + return nil, err + } + } + net := netObj.(*SNetwork) + if net.IsOwner(userCred) || net.IsPublic || db.IsAdminAllowGet(userCred, net) { + netConfig.Network = netObj.GetId() + } else { + return nil, httperrors.NewForbiddenError("no allow to access network %s", p) } - netConfig.Network = netObj.GetId() } } if netConfig.BwLimit == 0 { @@ -831,6 +856,7 @@ func (self *SNetwork) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSOND extra.Add(jsonutils.NewInt(int64(self.GetTotalNicCount())), "ports_used") extra.Add(jsonutils.NewInt(int64(self.GetGuestnicsCount())), "vnics") extra.Add(jsonutils.NewInt(int64(self.GetBaremetalNicsCount())), "bm_vnics") + extra.Add(jsonutils.NewInt(int64(self.GetLoadbalancerIpsCount())), "lb_vnics") extra.Add(jsonutils.NewInt(int64(self.GetGroupNicsCount())), "group_vnics") extra.Add(jsonutils.NewInt(int64(self.GetReservedNicsCount())), "reserve_vnics") @@ -891,24 +917,35 @@ func (self *SNetwork) AllowPerformReserveIp(ctx context.Context, userCred mcclie } func (self *SNetwork) PerformReserveIp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - ipstr, _ := data.GetString("ip") - notes, _ := data.GetString("notes") - if len(ipstr) == 0 || len(notes) == 0 { - return nil, httperrors.NewInputParameterError("both reserved ip and notes should be provided") - } - ipAddr, err := netutils.NewIPV4Addr(ipstr) + ips, err := data.GetArray("ips") if err != nil { - return nil, httperrors.NewInputParameterError("not a valid ip address %s: %s", ipstr, err) + if data.Contains("ip") { + ip, _ := data.Get("ip") + ips = []jsonutils.JSONObject{ip} + } else { + return nil, httperrors.NewMissingParameterError("ips") + } } - if !self.isAddressInRange(ipAddr) { - return nil, httperrors.NewInputParameterError("Address %s not in network", ipstr) - } - if self.isAddressUsed(ipstr) { - return nil, httperrors.NewConflictError("Address %s has been used", ipstr) - } - err = ReservedipManager.ReserveIP(userCred, self, ipstr, notes) + notes, err := data.GetString("notes") if err != nil { - return nil, err + return nil, httperrors.NewMissingParameterError("ips") + } + for _, ip := range ips { + ipstr, _ := ip.GetString() + ipAddr, err := netutils.NewIPV4Addr(ipstr) + if err != nil { + return nil, httperrors.NewInputParameterError("not a valid ip address %s: %s", ipstr, err) + } + if !self.isAddressInRange(ipAddr) { + return nil, httperrors.NewInputParameterError("Address %s not in network", ipstr) + } + if self.isAddressUsed(ipstr) { + return nil, httperrors.NewConflictError("Address %s has been used", ipstr) + } + err = ReservedipManager.ReserveIP(userCred, self, ipstr, notes) + if err != nil { + return nil, err + } } return nil, nil } @@ -1045,7 +1082,19 @@ func (manager *SNetworkManager) ValidateCreateData(ctx context.Context, userCred } vpc := vpcObj.(*SVpc) zone := zoneObj.(*SZone) - wires, err := WireManager.getWiresByVpcAndZone(vpc, zone) + region := zone.GetRegion() + if region == nil { + return nil, httperrors.NewInternalServerError("zone %s related region not found", zone.Id) + } + + // 华为云wire zone_id 为空 + var wires []SWire + if region.Provider == CLOUD_PROVIDER_HUAWEI { + wires, err = WireManager.getWiresByVpcAndZone(vpc, nil) + } else { + wires, err = WireManager.getWiresByVpcAndZone(vpc, zone) + } + if err != nil { if err == sql.ErrNoRows { return nil, httperrors.NewNotFoundError("wire not found for zone %s and vpc %s", zoneStr, vpcStr) @@ -1095,8 +1144,8 @@ func (manager *SNetworkManager) ValidateCreateData(ctx context.Context, userCred serverTypeStr, _ := data.GetString("server_type") if len(serverTypeStr) == 0 { - serverTypeStr = SERVER_TYPE_GUEST - } else if !sets.NewString(SERVER_TYPE_GUEST, SERVER_TYPE_BAREMETAL, SERVER_TYPE_CONTAINER).Has(serverTypeStr) { + serverTypeStr = NETWORK_TYPE_GUEST + } else if !utils.IsInStringArray(serverTypeStr, ALL_NETWORK_TYPES) { return nil, httperrors.NewInputParameterError("Invalid server_type: %s", serverTypeStr) } data.Add(jsonutils.NewString(serverTypeStr), "server_type") @@ -1222,7 +1271,7 @@ func isOverlapNetworks(nets []SNetwork, startIp netutils.IPV4Addr, endIp netutil } func (self *SNetwork) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - if db.IsAdminAllowCreate(userCred, self.GetModelManager()) && ownerProjId == userCred.GetProjectId() { + if db.IsAdminAllowCreate(userCred, self.GetModelManager()) && ownerProjId == userCred.GetProjectId() && self.ServerType == NETWORK_TYPE_GUEST { self.IsPublic = true } else { self.IsPublic = false @@ -1437,6 +1486,24 @@ func (manager *SNetworkManager) ListItemFilter(ctx context.Context, q *sqlchemy. q = q.Filter(sqlchemy.In(q.Field("wire_id"), subq.SubQuery())) } + if query.Contains("is_private") && jsonutils.QueryBoolean(query, "is_private", false) { + wires := WireManager.Query().SubQuery() + vpcs := VpcManager.Query().SubQuery() + subq := wires.Query(wires.Field("id")) + subq = subq.Join(vpcs, sqlchemy.Equals(vpcs.Field("id"), wires.Field("vpc_id"))) + subq = subq.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id"))) + q = q.Filter(sqlchemy.In(q.Field("wire_id"), subq.SubQuery())) + } + + if query.Contains("is_public") && jsonutils.QueryBoolean(query, "is_public", false) { + wires := WireManager.Query().SubQuery() + vpcs := VpcManager.Query().SubQuery() + subq := wires.Query(wires.Field("id")) + subq = subq.Join(vpcs, sqlchemy.Equals(vpcs.Field("id"), wires.Field("vpc_id"))) + subq = subq.Filter(sqlchemy.IsNotEmpty(vpcs.Field("manager_id"))) + q = q.Filter(sqlchemy.In(q.Field("wire_id"), subq.SubQuery())) + } + return q, nil } @@ -1529,7 +1596,7 @@ func (self *SNetwork) PerformMerge(ctx context.Context, userCred mcclient.TokenC if ipNE.StepUp() == ipSS { startIp, endIp = net.GuestIpStart, self.GuestIpEnd - } else if ipSE == ipNS { + } else if ipSE.StepUp() == ipNS { startIp, endIp = self.GuestIpStart, net.GuestIpEnd } else { note := "Incontinuity Network for %s and %s" @@ -1560,10 +1627,13 @@ func (self *SNetwork) PerformMerge(ctx context.Context, userCred mcclient.TokenC for _, gn := range guestnetworks { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if self.isAddressInRange(addr) { - GuestnetworkManager.TableSpec().Update(gn, func() error { + _, err = GuestnetworkManager.TableSpec().Update(&gn, func() error { gn.NetworkId = net.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1576,10 +1646,13 @@ func (self *SNetwork) PerformMerge(ctx context.Context, userCred mcclient.TokenC for _, gn := range hostnetworks { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if self.isAddressInRange(addr) { - HostnetworkManager.TableSpec().Update(gn, func() error { + _, err = HostnetworkManager.TableSpec().Update(&gn, func() error { gn.NetworkId = net.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1592,10 +1665,13 @@ func (self *SNetwork) PerformMerge(ctx context.Context, userCred mcclient.TokenC for _, gn := range reservedips { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if self.isAddressInRange(addr) { - ReservedipManager.TableSpec().Update(gn, func() error { + _, err = ReservedipManager.TableSpec().Update(&gn, func() error { gn.NetworkId = net.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1608,10 +1684,13 @@ func (self *SNetwork) PerformMerge(ctx context.Context, userCred mcclient.TokenC for _, gn := range groupnetwroks { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if self.isAddressInRange(addr) { - GroupnetworkManager.TableSpec().Update(gn, func() error { + _, err = GroupnetworkManager.TableSpec().Update(&gn, func() error { gn.NetworkId = net.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1700,10 +1779,13 @@ func (self *SNetwork) PerformSplit(ctx context.Context, userCred mcclient.TokenC for _, gn := range guestnetworks { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if network.isAddressInRange(addr) { - GuestnetworkManager.TableSpec().Update(gn, func() error { + _, err := GuestnetworkManager.TableSpec().Update(&gn, func() error { gn.NetworkId = network.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1716,10 +1798,13 @@ func (self *SNetwork) PerformSplit(ctx context.Context, userCred mcclient.TokenC for _, gn := range hostnetworks { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if network.isAddressInRange(addr) { - HostnetworkManager.TableSpec().Update(gn, func() error { + _, err = HostnetworkManager.TableSpec().Update(&gn, func() error { gn.NetworkId = network.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1732,10 +1817,13 @@ func (self *SNetwork) PerformSplit(ctx context.Context, userCred mcclient.TokenC for _, gn := range reservedips { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if network.isAddressInRange(addr) { - ReservedipManager.TableSpec().Update(gn, func() error { + _, err = ReservedipManager.TableSpec().Update(&gn, func() error { gn.NetworkId = network.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1748,10 +1836,13 @@ func (self *SNetwork) PerformSplit(ctx context.Context, userCred mcclient.TokenC for _, gn := range groupnetworks { addr, _ := netutils.NewIPV4Addr(gn.IpAddr) if network.isAddressInRange(addr) { - GroupnetworkManager.TableSpec().Update(gn, func() error { + _, err = GroupnetworkManager.TableSpec().Update(&gn, func() error { gn.NetworkId = network.Id return nil }) + if err != nil { + log.Errorln(err) + } } } @@ -1761,3 +1852,11 @@ func (self *SNetwork) PerformSplit(ctx context.Context, userCred mcclient.TokenC db.OpsLog.LogEvent(network, db.ACT_CREATE, map[string]string{"network": self.Id}, userCred) return nil, nil } + +func (network *SNetwork) getAllocTimoutDuration() time.Duration { + tos := network.AllocTimoutSeconds + if tos < options.Options.MinimalIpAddrReusedIntervalSeconds { + tos = options.Options.MinimalIpAddrReusedIntervalSeconds + } + return time.Duration(tos) * time.Second +} diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go new file mode 100644 index 0000000000..63d45408d4 --- /dev/null +++ b/pkg/compute/models/regiondrivers.go @@ -0,0 +1,75 @@ +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type IRegionDriver interface { + GetProvider() string + + ValidateCreateLoadbalancerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, task taskman.ITask) error + RequestDeleteLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, task taskman.ITask) error + RequestStartLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, task taskman.ITask) error + RequestStopLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, task taskman.ITask) error + RequestSyncstatusLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, task taskman.ITask) error + + ValidateCreateLoadbalancerAclData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *SLoadbalancerAcl, task taskman.ITask) error + RequestDeleteLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *SLoadbalancerAcl, task taskman.ITask) error + RequestSyncLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *SLoadbalancerAcl, task taskman.ITask) error + + ValidateCreateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) + ValidateUpdateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *SLoadbalancerCertificate, task taskman.ITask) error + RequestDeleteLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *SLoadbalancerCertificate, task taskman.ITask) error + + ValidateCreateLoadbalancerBackendGroupData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, lb *SLoadbalancer, backends []cloudprovider.SLoadbalancerBackend) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend, task taskman.ITask) error + RequestDeleteLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *SLoadbalancerBackendGroup, task taskman.ITask) error + ValidateDeleteLoadbalancerBackendGroupCondition(ctx context.Context, lbbb *SLoadbalancerBackendGroup) error + + ValidateCreateLoadbalancerBackendData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendType string, lb *SLoadbalancer, backendGroup *SLoadbalancerBackendGroup, backend db.IModel) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *SLoadbalancerBackend, task taskman.ITask) error + RequestDeleteLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *SLoadbalancerBackend, task taskman.ITask) error + ValidateDeleteLoadbalancerBackendCondition(ctx context.Context, lbb *SLoadbalancerBackend) error + + ValidateCreateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) + ValidateUpdateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + RequestDeleteLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + RequestStartLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + RequestStopLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + RequestSyncstatusLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + RequestSyncLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + + ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) + RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *SLoadbalancerListenerRule, task taskman.ITask) error + RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *SLoadbalancerListenerRule, task taskman.ITask) error +} + +var regionDrivers map[string]IRegionDriver + +func init() { + regionDrivers = make(map[string]IRegionDriver) +} + +func RegisterRegionDriver(driver IRegionDriver) { + regionDrivers[driver.GetProvider()] = driver +} + +func GetRegionDriver(provider string) IRegionDriver { + driver, ok := regionDrivers[provider] + if ok { + return driver + } + log.Fatalf("Unsupported provider %s", provider) + return nil +} diff --git a/pkg/compute/models/secgrouprules.go b/pkg/compute/models/secgrouprules.go index a86b0c35e7..1a32ef1d5b 100644 --- a/pkg/compute/models/secgrouprules.go +++ b/pkg/compute/models/secgrouprules.go @@ -401,12 +401,17 @@ func (manager *SSecurityGroupRuleManager) newFromCloudSecurityGroup(rule secrule protocol = secrules.PROTO_ANY } + cidr := "0.0.0.0/0" + if rule.IPNet != nil && rule.IPNet.String() != "" { + cidr = rule.IPNet.String() + } + secrule := &SSecurityGroupRule{ Priority: int64(rule.Priority), Protocol: protocol, Ports: "", Direction: string(rule.Direction), - CIDR: rule.IPNet.String(), + CIDR: cidr, Action: string(rule.Action), Description: rule.Description, SecgroupID: secgroup.Id, diff --git a/pkg/compute/models/secgroups.go b/pkg/compute/models/secgroups.go index ddc1c311c9..b53dd3f62f 100644 --- a/pkg/compute/models/secgroups.go +++ b/pkg/compute/models/secgroups.go @@ -501,8 +501,7 @@ func (self *SSecurityGroup) StartDeleteSecurityGroupTask(ctx context.Context, us } func (self *SSecurityGroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { - log.Infof("SecurityGroup delete do nothing") - return nil + return self.SSharableVirtualResourceBase.DoPendingDelete(ctx, userCred) } func (self *SSecurityGroup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index d4dc282d83..cdfa96b6be 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -40,7 +40,7 @@ type SSnapshot struct { db.SVirtualResourceBase SManagedResourceBase - DiskId string `width:"36" charset:"ascii" nullable:"true" create:"required" key_index:"true" list:"user"` + DiskId string `width:"36" charset:"ascii" nullable:"true" create:"required" list:"user"` StorageId string `width:"36" charset:"ascii" nullable:"true" list:"admin"` CreatedBy string `width:"36" charset:"ascii" nullable:"false" default:"manual" list:"admin"` Location string `charset:"ascii" nullable:"true" list:"admin"` diff --git a/pkg/compute/models/storagecachedimages.go b/pkg/compute/models/storagecachedimages.go index ff48453dec..5385ea5359 100644 --- a/pkg/compute/models/storagecachedimages.go +++ b/pkg/compute/models/storagecachedimages.go @@ -2,7 +2,9 @@ package models import ( "context" + "database/sql" "fmt" + "strings" "time" "github.com/serialx/hashring" @@ -14,6 +16,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -53,8 +56,8 @@ func init() { type SStoragecachedimage struct { db.SJointResourceBase - StoragecacheId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required" key_index:"true"` - CachedimageId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required" key_index:"true"` + StoragecacheId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + CachedimageId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` ExternalId string `width:"256" charset:"utf8" nullable:"false" get:"admin"` @@ -209,9 +212,11 @@ func (self *SStoragecachedimage) getReferenceCount() int { } func (manager *SStoragecachedimageManager) GetStoragecachedimage(cacheId string, imageId string) *SStoragecachedimage { - obj, err := manager.FetchByIds(cacheId, imageId) + obj, err := db.FetchJointByIds(manager, cacheId, imageId, nil) if err != nil { - log.Errorf("%s", err) + if err != sql.ErrNoRows { + log.Errorf("manager.FetchByIds %s %s error %s", cacheId, imageId, err) + } return nil } return obj.(*SStoragecachedimage) @@ -229,6 +234,10 @@ func (self *SStoragecachedimage) ValidateDeleteCondition(ctx context.Context) er if self.getReferenceCount() > 0 { return httperrors.NewNotEmptyError("Image is in use") } + return self.SJointResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SStoragecachedimage) isCachedImageInUse() error { if !self.isDownloadSessionExpire() { return httperrors.NewResourceBusyError("Active download session not expired") } @@ -236,7 +245,7 @@ func (self *SStoragecachedimage) ValidateDeleteCondition(ctx context.Context) er if image != nil && !image.canDeleteLastCache() { return httperrors.NewResourceBusyError("Cannot delete the last cache") } - return self.SJointResourceBase.ValidateDeleteCondition(ctx) + return nil } func (self *SStoragecachedimage) isDownloadSessionExpire() bool { @@ -252,6 +261,12 @@ func (self *SStoragecachedimage) markDeleting(ctx context.Context, userCred mccl if err != nil { return err } + if !isForce { + err = self.isCachedImageInUse() + if err != nil { + return err + } + } cache := self.GetStoragecache() image := self.GetCachedimage() @@ -272,7 +287,7 @@ func (self *SStoragecachedimage) markDeleting(ctx context.Context, userCred mccl return err } -func (manager *SStoragecachedimageManager) Register(ctx context.Context, userCred mcclient.TokenCredential, cacheId, imageId string) *SStoragecachedimage { +func (manager *SStoragecachedimageManager) Register(ctx context.Context, userCred mcclient.TokenCredential, cacheId, imageId string, status string) *SStoragecachedimage { lockman.LockClass(ctx, manager, userCred.GetProjectId()) defer lockman.ReleaseClass(ctx, manager, userCred.GetProjectId()) @@ -286,7 +301,10 @@ func (manager *SStoragecachedimageManager) Register(ctx context.Context, userCre cachedimage.StoragecacheId = cacheId cachedimage.CachedimageId = imageId - cachedimage.Status = CACHED_IMAGE_STATUS_INIT + if len(status) == 0 { + status = CACHED_IMAGE_STATUS_INIT + } + cachedimage.Status = status err := manager.TableSpec().Insert(cachedimage) @@ -336,3 +354,58 @@ func (self *SStoragecachedimage) SetExternalId(externalId string) error { }) return err } + +func (self SStoragecachedimage) GetExternalId() string { + return self.ExternalId +} + +func (self *SStoragecachedimage) syncWithCloudImage(ctx context.Context, userCred mcclient.TokenCredential, image cloudprovider.ICloudImage) error { + cachedImage := self.GetCachedimage() + if len(cachedImage.ExternalId) > 0 { + return cachedImage.syncWithCloudImage(ctx, userCred, image) + } else { + return nil + } +} + +func (manager *SStoragecachedimageManager) newFromCloudImage(ctx context.Context, userCred mcclient.TokenCredential, image cloudprovider.ICloudImage, cache *SStoragecache) error { + var cachedImage *SCachedimage + imgObj, err := CachedimageManager.FetchByExternalId(image.GetGlobalId()) + if err != nil { + if err != sql.ErrNoRows { + // unhandled error + log.Errorf("CachedimageManager.FetchByExternalId error %s", err) + return err + } + // not found + // first test if this image is uploaded by onecloud, if true, image name should be ID of onecloud image + name := image.GetName() + if utils.IsAscii(name) { + if strings.HasPrefix(name, "img") { + name = name[3:] + } + imgObj, err = CachedimageManager.FetchById(name) + if err == nil && imgObj != nil { + cachedImage = imgObj.(*SCachedimage) + } + } + if cachedImage == nil { + // no such image + cachedImage, err = CachedimageManager.newFromCloudImage(ctx, userCred, image) + if err != nil { + log.Errorf("CachedimageManager.newFromCloudImage fail %s", err) + return err + } + } + } else { + cachedImage = imgObj.(*SCachedimage) + } + if len(cachedImage.ExternalId) > 0 { + cachedImage.syncWithCloudImage(ctx, userCred, image) + } + scimg := manager.Register(ctx, userCred, cache.GetId(), cachedImage.GetId(), image.GetStatus()) + if scimg == nil { + return fmt.Errorf("register cached image fail") + } + return scimg.SetExternalId(image.GetGlobalId()) +} diff --git a/pkg/compute/models/storagecaches.go b/pkg/compute/models/storagecaches.go index 534ff8234a..6777f4bc37 100644 --- a/pkg/compute/models/storagecaches.go +++ b/pkg/compute/models/storagecaches.go @@ -9,6 +9,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/compare" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -200,10 +201,37 @@ func (self *SStoragecache) GetCustomizeColumns(ctx context.Context, userCred mcc return extra } +func (self *SStoragecache) getCachedImageList(excludeIds []string, imageType string) []SCachedimage { + images := make([]SCachedimage, 0) + + cachedImages := CachedimageManager.Query().SubQuery() + storagecachedImages := StoragecachedimageManager.Query().SubQuery() + + q := cachedImages.Query() + q = q.Join(storagecachedImages, sqlchemy.Equals(cachedImages.Field("id"), storagecachedImages.Field("cachedimage_id"))) + q = q.Filter(sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), self.Id)) + + if len(excludeIds) > 0 { + q = q.Filter(sqlchemy.NotIn(cachedImages.Field("id"), excludeIds)) + } + if len(imageType) > 0 { + q = q.Filter(sqlchemy.Equals(cachedImages.Field("image_type"), imageType)) + } + + err := db.FetchModelObjects(CachedimageManager, q, &images) + if err != nil { + if err != sql.ErrNoRows { + log.Errorf("%s", err) + } + return nil + } + return images +} + func (self *SStoragecache) getCachedImages() []SStoragecachedimage { images := make([]SStoragecachedimage, 0) q := StoragecachedimageManager.Query().Equals("storagecache_id", self.Id) - err := q.All(&images) + err := db.FetchModelObjects(StoragecachedimageManager, q, &images) if err != nil { log.Errorf("%s", err) return nil @@ -239,7 +267,7 @@ func (self *SStoragecache) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils. } func (self *SStoragecache) StartImageCacheTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, format string, isForce bool, parentTaskId string) error { - StoragecachedimageManager.Register(ctx, userCred, self.Id, imageId) + StoragecachedimageManager.Register(ctx, userCred, self.Id, imageId, "") data := jsonutils.NewDict() data.Add(jsonutils.NewString(imageId), "image_id") if len(format) > 0 { @@ -255,6 +283,7 @@ func (self *SStoragecache) StartImageCacheTask(ctx context.Context, userCred mcc data.Add(jsonutils.NewString(imgInfo.OsArch), "os_arch") data.Add(jsonutils.NewString(imgInfo.OsDistro), "os_distribution") data.Add(jsonutils.NewString(imgInfo.OsVersion), "os_version") + data.Add(jsonutils.NewString(imgInfo.OsFullVersion), "os_full_version") } if isForce { @@ -269,11 +298,11 @@ func (self *SStoragecache) StartImageCacheTask(ctx context.Context, userCred mcc return nil } -func (self *SStoragecache) StartImageUncacheTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, isForce bool, parentTaskId string) error { +func (self *SStoragecache) StartImageUncacheTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, isPurge bool, parentTaskId string) error { data := jsonutils.NewDict() data.Add(jsonutils.NewString(imageId), "image_id") - if isForce { - data.Add(jsonutils.JSONTrue, "is_force") + if isPurge { + data.Add(jsonutils.JSONTrue, "is_purge") } task, err := taskman.TaskManager.NewTask(ctx, "StorageUncacheImageTask", self, userCred, data, parentTaskId, "", nil) if err != nil { @@ -349,19 +378,31 @@ func (self *SStoragecache) PerformUncacheImage(ctx context.Context, userCred mcc if len(imageStr) == 0 { return nil, httperrors.NewInputParameterError("missing image id or name") } + isForce := jsonutils.QueryBoolean(data, "is_force", false) var imageId string - image, err := CachedimageManager.getImageInfo(ctx, userCred, imageStr, isForce) + + imgObj, err := CachedimageManager.FetchByIdOrName(nil, imageStr) if err != nil { - log.Infof("image %s not found %s", imageStr, err) - if !isForce { - return nil, httperrors.NewImageNotFoundError(imageStr) + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CachedimageManager.Keyword(), imageStr) } else { - imageId = imageStr + return nil, httperrors.NewGeneralError(err) } } else { - imageId = image.Id + cachedImage := imgObj.(*SCachedimage) + if cachedImage.ImageType != cloudprovider.CachedImageTypeCustomized && !isForce { + return nil, httperrors.NewForbiddenError("cannot uncache non-customized images") + } + imageId = imgObj.GetId() + _, err := CachedimageManager.getImageInfo(ctx, userCred, imageStr, isForce) + if err != nil { + log.Infof("image %s not found %s", imageStr, err) + if !isForce { + return nil, httperrors.NewImageNotFoundError(imageStr) + } + } } scimg := StoragecachedimageManager.GetStoragecachedimage(self.Id, imageId) @@ -369,7 +410,7 @@ func (self *SStoragecache) PerformUncacheImage(ctx context.Context, userCred mcc return nil, httperrors.NewResourceNotFoundError("storage not cache image") } - if scimg.Status == CACHED_IMAGE_STATUS_INIT { + if scimg.Status == CACHED_IMAGE_STATUS_INIT || isForce { err = scimg.Detach(ctx, userCred) return nil, err } @@ -410,3 +451,96 @@ func (self *SStoragecache) PerformCacheImage(ctx context.Context, userCred mccli err = self.StartImageCacheTask(ctx, userCred, image.Id, format, isForce, "") return nil, err } + +func (cache *SStoragecache) SyncCloudImages( + ctx context.Context, + userCred mcclient.TokenCredential, + iStoragecache cloudprovider.ICloudStoragecache, +) compare.SyncResult { + syncResult := compare.SyncResult{} + + localCachedImages := cache.getCachedImages() + log.Debugf("localCachedImages %d", len(localCachedImages)) + + remoteImages, err := iStoragecache.GetIImages() + if err != nil { + log.Errorf("fail to get images %s", err) + syncResult.Error(err) + return syncResult + } + + removed := make([]SStoragecachedimage, 0) + commondb := make([]SStoragecachedimage, 0) + commonext := make([]cloudprovider.ICloudImage, 0) + added := make([]cloudprovider.ICloudImage, 0) + + err = compare.CompareSets(localCachedImages, remoteImages, &removed, &commondb, &commonext, &added) + if err != nil { + log.Errorf("compare.CompareSets error %s", err) + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i += 1 { + image := removed[i].GetCachedimage() + err := removed[i].Detach(ctx, userCred) + if err != nil { + log.Errorf("storagecachedimage %s %s detach fail %s", removed[i].StoragecacheId, removed[i].CachedimageId, err) + syncResult.DeleteError(err) + } else { + syncResult.Delete() + if image != nil && image.getStoragecacheCount() == 0 { + err = image.Delete(ctx, userCred) + if err != nil { + log.Errorf("image delete error %s", err) + } + } + } + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].syncWithCloudImage(ctx, userCred, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + } else { + syncResult.Update() + } + } + for i := 0; i < len(added); i += 1 { + err = StoragecachedimageManager.newFromCloudImage(ctx, userCred, added[i], cache) + if err != nil { + syncResult.AddError(err) + } else { + syncResult.Add() + } + } + + return syncResult +} + +func (self *SStoragecache) IsReachCapacityLimit(imageId string) bool { + imgObj, _ := CachedimageManager.FetchById(imageId) + if imgObj == nil { + return false + } + cachedImage := imgObj.(*SCachedimage) + if cachedImage.ImageType != cloudprovider.CachedImageTypeCustomized { + // no need to cache + return false + } + cachedImages := self.getCachedImageList([]string{imageId}, cloudprovider.CachedImageTypeCustomized) + host, _ := self.GetHost() + return host.GetHostDriver().IsReachStoragecacheCapacityLimit(host, cachedImages) +} + +func (self *SStoragecache) StartRelinquishLeastUsedCachedImageTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, parentTaskId string) error { + cachedImages := self.getCachedImageList([]string{imageId}, cloudprovider.CachedImageTypeCustomized) + leastUsedIdx := -1 + leastRefCount := -1 + for i := range cachedImages { + if leastRefCount < 0 || leastRefCount > cachedImages[i].RefCount { + leastRefCount = cachedImages[i].RefCount + leastUsedIdx = i + } + } + return self.StartImageUncacheTask(ctx, userCred, cachedImages[leastUsedIdx].GetId(), false, parentTaskId) +} diff --git a/pkg/compute/models/storages.go b/pkg/compute/models/storages.go index e41f967318..d0e46ebb0a 100644 --- a/pkg/compute/models/storages.go +++ b/pkg/compute/models/storages.go @@ -62,6 +62,9 @@ const ( STORAGE_HUAWEI_SSD = "SSD" // 超高IO云硬盘 STORAGE_HUAWEI_SAS = "SAS" // 高IO云硬盘 STORAGE_HUAWEI_SATA = "SATA" // 普通IO云硬盘 + + // openstack + STORAGE_OPENSTACK_ISCSI = "iscsi" ) const ( @@ -90,6 +93,7 @@ var ( STORAGE_STANDARD_LRS, STORAGE_STANDARDSSD_LRS, STORAGE_PREMIUM_LRS, STORAGE_GP2_SSD, STORAGE_IO1_SSD, STORAGE_ST1_HDD, STORAGE_SC1_HDD, STORAGE_STANDARD_HDD, STORAGE_LOCAL_BASIC, STORAGE_LOCAL_SSD, STORAGE_CLOUD_BASIC, STORAGE_CLOUD_PREMIUM, + STORAGE_HUAWEI_SSD, STORAGE_HUAWEI_SAS, STORAGE_HUAWEI_SATA, } STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS} @@ -129,6 +133,9 @@ type SStorage struct { Enabled bool `nullable:"false" default:"true" list:"user" create:"optional"` Status string `width:"36" charset:"ascii" nullable:"false" default:"offline" list:"user" create:"optional"` + + // indicating whether system disk can be allocated in this storage + IsSysDiskStore bool `nullable:"false" default:"true" list:"user" create:"optional" update:"admin"` } func (manager *SStorageManager) GetContextManager() []db.IModelManager { @@ -741,6 +748,8 @@ func (self *SStorage) syncWithCloudStorage(extStorage cloudprovider.ICloudStorag self.IsEmulated = extStorage.IsEmulated() self.ManagerId = extStorage.GetManagerId() + self.IsSysDiskStore = extStorage.IsSysDiskStore() + return nil }) if err != nil { @@ -768,6 +777,8 @@ func (manager *SStorageManager) newFromCloudStorage(extStorage cloudprovider.ICl storage.IsEmulated = extStorage.IsEmulated() storage.ManagerId = extStorage.GetManagerId() + storage.IsSysDiskStore = extStorage.IsSysDiskStore() + err := manager.TableSpec().Insert(&storage) if err != nil { log.Errorf("newFromCloudStorage fail %s", err) diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 3468dca8b5..8599fa687e 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -102,7 +102,7 @@ func (self *SVpc) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCr } func (self *SVpc) ValidateDeleteCondition(ctx context.Context) error { - if self.GetNetworkCount() > 0 || self.GetRouteTableCount() > 0 { + if self.GetNetworkCount() > 0 { return httperrors.NewNotEmptyError("VPC not empty") } if self.Id == DEFAULT_VPC_ID { @@ -150,8 +150,19 @@ func (self *SVpc) GetNetworkCount() int { return q.Count() } +func (self *SVpc) GetRouteTableQuery() *sqlchemy.SQuery { + return RouteTableManager.Query().Equals("vpc_id", self.Id) +} + +func (self *SVpc) GetRouteTables() []SRouteTable { + q := self.GetRouteTableQuery() + routes := []SRouteTable{} + db.FetchModelObjects(RouteTableManager, q, &routes) + return routes +} + func (self *SVpc) GetRouteTableCount() int { - return RouteTableManager.Query().Equals("vpc_id", self.Id).Count() + return self.GetRouteTableQuery().Count() } func (self *SVpc) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { @@ -482,6 +493,10 @@ func (self *SVpc) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCr func (self *SVpc) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { db.OpsLog.LogEvent(self, db.ACT_DELOCATE, self.GetShortDesc(ctx), userCred) self.SetStatus(userCred, VPC_STATUS_DELETED, "real delete") + routes := self.GetRouteTables() + for i := 0; i < len(routes); i++ { + routes[i].RealDelete(ctx, userCred) + } return self.SEnabledStatusStandaloneResourceBase.Delete(ctx, userCred) } diff --git a/pkg/compute/models/wires.go b/pkg/compute/models/wires.go index 8c3a057d27..c3b08b1903 100644 --- a/pkg/compute/models/wires.go +++ b/pkg/compute/models/wires.go @@ -40,9 +40,9 @@ func init() { type SWire struct { db.SStandaloneResourceBase - Bandwidth int `list:"admin" update:"admin" nullable:"false" create:"admin_required"` // = Column(Integer, nullable=False) # bandwidth of network in Mbps - ScheduleRank int `list:"admin" update:"admin"` // = Column(Integer, default=0, nullable=True) - ZoneId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // = Column(VARCHAR(36, charset='ascii'), nullable=False) + Bandwidth int `list:"admin" update:"admin" nullable:"false" create:"admin_required"` // = Column(Integer, nullable=False) # bandwidth of network in Mbps + ScheduleRank int `list:"admin" update:"admin"` // = Column(Integer, default=0, nullable=True) + ZoneId string `width:"36" charset:"ascii" nullable:"true" list:"admin" create:"admin_required"` // = Column(VARCHAR(36, charset='ascii'), nullable=False) VpcId string `wdith:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` } @@ -259,16 +259,20 @@ func (manager *SWireManager) newFromCloudWire(extWire cloudprovider.ICloudWire, wire.ExternalId = extWire.GetGlobalId() wire.Bandwidth = extWire.GetBandwidth() wire.VpcId = vpc.Id - zoneObj, err := ZoneManager.FetchByExternalId(extWire.GetIZone().GetGlobalId()) - if err != nil { - log.Errorf("cannot find zone for wire %s", err) - return nil, err + izone := extWire.GetIZone() + if izone != nil { + zoneObj, err := ZoneManager.FetchByExternalId(izone.GetGlobalId()) + if err != nil { + log.Errorf("cannot find zone for wire %s", err) + return nil, err + } + + wire.ZoneId = zoneObj.(*SZone).Id } - wire.ZoneId = zoneObj.(*SZone).Id wire.IsEmulated = extWire.IsEmulated() - err = manager.TableSpec().Insert(&wire) + err := manager.TableSpec().Insert(&wire) if err != nil { log.Errorf("newFromCloudWire fail %s", err) return nil, err @@ -408,20 +412,20 @@ func (self *SWire) getPrivateNetworks(userCred mcclient.TokenCredential) ([]SNet return nets, nil } -func (self *SWire) GetCandidatePrivateNetwork(userCred mcclient.TokenCredential, isExit bool, serverType string) (*SNetwork, error) { +func (self *SWire) GetCandidatePrivateNetwork(userCred mcclient.TokenCredential, isExit bool, serverTypes []string) (*SNetwork, error) { nets, err := self.getPrivateNetworks(userCred) if err != nil { return nil, err } - return ChooseCandidateNetworks(nets, isExit, serverType), nil + return ChooseCandidateNetworks(nets, isExit, serverTypes), nil } -func (self *SWire) GetCandidatePublicNetwork(isExit bool, serverType string) (*SNetwork, error) { +func (self *SWire) GetCandidatePublicNetwork(isExit bool, serverTypes []string) (*SNetwork, error) { nets, err := self.getPublicNetworks() if err != nil { return nil, err } - return ChooseCandidateNetworks(nets, isExit, serverType), nil + return ChooseCandidateNetworks(nets, isExit, serverTypes), nil } func (self *SWire) GetCandidateNetworkForIp(userCred mcclient.TokenCredential, ipAddr string) (*SNetwork, error) { @@ -472,7 +476,17 @@ func chooseNetworkByAddressCount(nets []*SNetwork) (*SNetwork, *SNetwork) { return minSel, maxSel } -func ChooseCandidateNetworks(nets []SNetwork, isExit bool, serverType string) *SNetwork { +func ChooseCandidateNetworks(nets []SNetwork, isExit bool, serverTypes []string) *SNetwork { + for _, s := range serverTypes { + net := chooseCandidateNetworksByNetworkType(nets, isExit, s) + if net != nil { + return net + } + } + return nil +} + +func chooseCandidateNetworksByNetworkType(nets []SNetwork, isExit bool, serverType string) *SNetwork { matchingNets := make([]*SNetwork, 0) notMatchingNets := make([]*SNetwork, 0) @@ -481,7 +495,7 @@ func ChooseCandidateNetworks(nets []SNetwork, isExit bool, serverType string) *S if isExit != net.IsExitNetwork() { continue } - if serverType == net.ServerType || (len(net.ServerType) == 0 && serverType == SERVER_TYPE_GUEST) { + if serverType == net.ServerType || (len(net.ServerType) == 0 && serverType == NETWORK_TYPE_GUEST) { matchingNets = append(matchingNets, &net) } else { notMatchingNets = append(notMatchingNets, &net) diff --git a/pkg/compute/models/zones.go b/pkg/compute/models/zones.go index 3e65ec5d3e..df269437c4 100644 --- a/pkg/compute/models/zones.go +++ b/pkg/compute/models/zones.go @@ -17,6 +17,7 @@ import ( ) const ( + ZONE_INIT = "init" ZONE_ENABLE = "enable" ZONE_DISABLE = "disable" ZONE_SOLDOUT = "soldout" @@ -469,7 +470,7 @@ func (manager *SZoneManager) InitializeData() error { return nil }) } - if z.Status == "init" { + if z.Status == ZONE_INIT || z.Status == ZONE_DISABLE { manager.TableSpec().Update(&z, func() error { z.Status = ZONE_ENABLE return nil @@ -479,6 +480,116 @@ func (manager *SZoneManager) InitializeData() error { return nil } +/* +Query 1: +vpc.manager_id is not empty && wire.zone_id is not empty +*/ +func (manager *SZoneManager) usableZoneQ1(providers, vpcs, wires, networks *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery { + // join tables + sq := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id"))) + if usableNet { + sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id"))) + } + sq = sq.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) + sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id"))) + + // add filters + if usableNet { + sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE)) + } + sq = sq.Filter(sqlchemy.IsNotEmpty(wires.Field("zone_id"))) + sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) + sq = sq.Filter(sqlchemy.In(providers.Field("status"), CLOUD_PROVIDER_VALID_STATUS)) + if usableVpc { + sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE)) + } + + return sq.SubQuery() +} + +/* +Query 2: +vpc.manager_id is empty && wire.zone_id is not empty +*/ +func (manager *SZoneManager) usableZoneQ2(vpcs, wires, networks *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery { + // join tables + sq := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id"))) + if usableNet { + sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id"))) + } + sq = sq.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) + + // add filters + if usableNet { + sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE)) + } + sq = sq.Filter(sqlchemy.IsNotEmpty(wires.Field("zone_id"))) + sq = sq.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id"))) + if usableVpc { + sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE)) + } + + return sq.SubQuery() +} + +/* +Query 3: +vpc.manager_id is not empty && wire.zone_id is empty + +2019.01.17 目前华为云子网在整个region 可用。wire中zone_id留空。 +*/ +func (manager *SZoneManager) usableZoneQ3(providers, vpcs, wires, networks, zones *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery { + // join tables + sq := zones.Query(sqlchemy.DISTINCT("zone_id", zones.Field("id"))) + sq = sq.Join(vpcs, sqlchemy.Equals(zones.Field("cloudregion_id"), vpcs.Field("cloudregion_id"))) + sq = sq.Join(wires, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) + if usableNet { + sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id"))) + } + sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id"))) + + // add filters + if usableNet { + sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE)) + } + sq = sq.Filter(sqlchemy.IsNullOrEmpty(wires.Field("zone_id"))) + sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) + sq = sq.Filter(sqlchemy.In(providers.Field("status"), CLOUD_PROVIDER_VALID_STATUS)) + if usableVpc { + sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE)) + } + + return sq.SubQuery() +} + +/* +Query 4: +vpc.manager_id is empty && wire.zone_id is empty + +2019.01.17 目前华为云子网在整个region 可用。wire中zone_id留空。 +*/ +func (manager *SZoneManager) usableZoneQ4(vpcs, wires, networks, zones *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery { + // join tables + sq := zones.Query(sqlchemy.DISTINCT("zone_id", zones.Field("id"))) + sq = sq.Join(vpcs, sqlchemy.Equals(zones.Field("cloudregion_id"), vpcs.Field("cloudregion_id"))) + sq = sq.Join(wires, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) + if usableNet { + sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id"))) + } + + // add filters + if usableNet { + sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE)) + } + sq = sq.Filter(sqlchemy.IsNullOrEmpty(wires.Field("zone_id"))) + sq = sq.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id"))) + if usableVpc { + sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE)) + } + + return sq.SubQuery() +} + func (manager *SZoneManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { q, err := manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) if err != nil { @@ -495,57 +606,56 @@ func (manager *SZoneManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQu } if jsonutils.QueryBoolean(query, "usable", false) || jsonutils.QueryBoolean(query, "usable_vpc", false) { + usableNet := jsonutils.QueryBoolean(query, "usable", false) + usableVpc := jsonutils.QueryBoolean(query, "usable_vpc", false) + networks := NetworkManager.Query().SubQuery() wires := WireManager.Query().SubQuery() vpcs := VpcManager.Query().SubQuery() providers := CloudproviderManager.Query().SubQuery() + zones := ZoneManager.Query().SubQuery() - usableNet := jsonutils.QueryBoolean(query, "usable", false) - usableVpc := jsonutils.QueryBoolean(query, "usable_vpc", false) - - sq := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id"))) - if usableNet { - sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id"))) - } - sq = sq.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) - sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id"))) - if usableNet { - sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE)) - } - sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled"))) - sq = sq.Filter(sqlchemy.In(providers.Field("status"), CLOUD_PROVIDER_VALID_STATUS)) - if usableVpc { - sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE)) - } - - sq2 := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id"))) - if usableNet { - sq2 = sq2.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id"))) - } - sq2 = sq2.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) - if usableNet { - sq2 = sq2.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE)) - } - sq2 = sq2.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id"))) - if usableVpc { - sq2 = sq2.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE)) - } + sq1 := manager.usableZoneQ1(providers, vpcs, wires, networks, usableNet, usableVpc) + sq2 := manager.usableZoneQ2(vpcs, wires, networks, usableNet, usableVpc) + sq3 := manager.usableZoneQ3(providers, vpcs, wires, networks, zones, usableNet, usableVpc) + sq4 := manager.usableZoneQ4(vpcs, wires, networks, zones, usableNet, usableVpc) q = q.Filter(sqlchemy.OR( - sqlchemy.In(q.Field("id"), sq.SubQuery()), - sqlchemy.In(q.Field("id"), sq2.SubQuery()), + sqlchemy.In(q.Field("id"), sq1), + sqlchemy.In(q.Field("id"), sq2), + sqlchemy.In(q.Field("id"), sq3), + sqlchemy.In(q.Field("id"), sq4), )) q = q.Equals("status", ZONE_ENABLE) } managerStr, _ := query.GetString("manager") if len(managerStr) > 0 { - provider := CloudproviderManager.FetchCloudproviderByIdOrName(managerStr) - if provider == nil { - return nil, httperrors.NewResourceNotFoundError("Cloud provider/manager %s not found", managerStr) + providerObj, err := CloudproviderManager.FetchByIdOrName(userCred, managerStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudproviderManager.Keyword(), managerStr) + } else { + return nil, httperrors.NewGeneralError(err) + } } + provider := providerObj.(*SCloudprovider) subq := CloudregionManager.Query("id").Equals("provider", provider.Provider).SubQuery() q = q.In("cloudregion_id", subq) } + accountStr, _ := query.GetString("account") + if len(accountStr) > 0 { + accountObj, err := CloudaccountManager.FetchByIdOrName(userCred, accountStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(CloudaccountManager.Keyword(), accountStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + account := accountObj.(*SCloudaccount) + subq := CloudregionManager.Query("id").Equals("provider", account.Provider).SubQuery() + q = q.In("cloudregion_id", subq) + } providerStr, _ := query.GetString("provider") if len(providerStr) > 0 { subq := CloudregionManager.Query("id").Equals("provider", providerStr).SubQuery() diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index 148e4ec0a5..5a96912ccf 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -20,7 +20,7 @@ type ComputeOptions struct { DefaultSecurityRules string `help:"Default security rules" default:"allow any"` DefaultAdminSecurityRules string `help:"Default admin security rules" default:""` - DefaultDiskSize int `default:"30720" help:"Default disk size in MB if not specified, default to 30GiB"` + DefaultDiskSizeMB int `default:"30720" help:"Default disk size in MB if not specified, default to 30GiB" json:"default_disk_size"` pending_delete.SPendingDeleteOptions @@ -76,6 +76,8 @@ type ComputeOptions struct { HostOfflineMaxSeconds int `help:"Maximal seconds interval that a host considered offline during which it did not ping region, default is 3 minues" default:"180"` HostOfflineDetectionInterval int `help:"Interval to check offline hosts, defualt is half a minute" default:"30"` + MinimalIpAddrReusedIntervalSeconds int `help:"Minimal seconds when a release IP address can be reallocate" default:"30"` + cloudcommon.CommonOptions cloudcommon.DBOptions } diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go new file mode 100644 index 0000000000..78bb06539a --- /dev/null +++ b/pkg/compute/regiondrivers/aliyun.go @@ -0,0 +1,220 @@ +package regiondrivers + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/pkg/utils" +) + +type SAliyunRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SAliyunRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SAliyunRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_ALIYUN +} + +func (self *SAliyunRegionDriver) ValidateCreateLoadbalancerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + loadbalancerSpec, _ := data.GetString("loadbalancer_spec") + if len(loadbalancerSpec) != 0 && !utils.IsInStringArray(loadbalancerSpec, []string{"slb.s1.small", "slb.s2.small", "slb.s2.mediu", "slb.s3.small", "slb.s3.mediu", "slb.s3.large"}) { + return nil, httperrors.NewInputParameterError("Unsupport loadbalancer_spec %s, support slb.s1.small、slb.s2.small、slb.s2.medium、slb.s3.small、slb.s3.medium、slb.s3.large", loadbalancerSpec) + } + return data, nil +} + +func (self *SAliyunRegionDriver) ValidateUpdateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + if data.Contains("certificate") || data.Contains("private_key") { + return nil, httperrors.NewUnsupportOperationError("Aliyun not allow to change certificate") + } + return data, nil +} + +func (self *SAliyunRegionDriver) ValidateDeleteLoadbalancerBackendCondition(ctx context.Context, lbb *models.SLoadbalancerBackend) error { + backendGroup := lbb.GetLoadbalancerBackendGroup() + if backendGroup.Type == models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE { + return httperrors.NewUnsupportOperationError("backend %s belong master slave backendgroup, not allow delete", lbb.Name) + } + return nil +} + +func (self *SAliyunRegionDriver) ValidateDeleteLoadbalancerBackendGroupCondition(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup) error { + if lbbg.Type == models.LB_BACKENDGROUP_TYPE_DEFAULT { + return httperrors.NewUnsupportOperationError("not allow to delete default backend group") + } + return nil +} + +func (self *SAliyunRegionDriver) ValidateCreateLoadbalancerBackendGroupData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, lb *models.SLoadbalancer, backends []cloudprovider.SLoadbalancerBackend) (*jsonutils.JSONDict, error) { + groupType, _ := data.GetString("type") + switch groupType { + case "", models.LB_BACKENDGROUP_TYPE_NORMAL: + break + case models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE: + if len(backends) != 2 { + return nil, httperrors.NewInputParameterError("master slave backendgorup must contain two backend") + } + default: + return nil, httperrors.NewInputParameterError("Unsupport backendgorup type %s", groupType) + } + for _, backend := range backends { + if len(backend.ExternalID) == 0 { + return nil, httperrors.NewInputParameterError("invalid guest %s", backend.Name) + } + if backend.Weight < 0 || backend.Weight > 100 { + return nil, httperrors.NewInputParameterError("Aliyun instance weight must be in the range of 0 ~ 100") + } + } + return data, nil +} + +func (self *SAliyunRegionDriver) ValidateCreateLoadbalancerBackendData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendType string, lb *models.SLoadbalancer, backendGroup *models.SLoadbalancerBackendGroup, backend db.IModel) (*jsonutils.JSONDict, error) { + if backendType != models.LB_BACKEND_GUEST { + return nil, httperrors.NewUnsupportOperationError("internal error: unexpected backend type %s", backendType) + } + if !utils.IsInStringArray(backendGroup.Type, []string{models.LB_BACKENDGROUP_TYPE_DEFAULT, models.LB_BACKENDGROUP_TYPE_NORMAL}) { + return nil, httperrors.NewUnsupportOperationError("backendgroup %s not support this operation", backendGroup.Name) + } + guest := backend.(*models.SGuest) + host := guest.GetHost() + if host == nil { + return nil, fmt.Errorf("error getting host of guest %s", guest.GetId()) + } + if lb == nil { + return nil, fmt.Errorf("error loadbalancer of backend group %s", backendGroup.GetId()) + } + hostRegion := host.GetRegion() + lbRegion := lb.GetRegion() + if hostRegion.Id != lbRegion.Id { + return nil, httperrors.NewInputParameterError("region of host %q (%s) != region of loadbalancer %q (%s))", + host.Name, host.ZoneId, lb.Name, lb.ZoneId) + } + address, err := models.LoadbalancerBackendManager.GetGuestAddress(guest) + if err != nil { + return nil, err + } + data.Set("address", jsonutils.NewString(address)) + weight, _ := data.Int("weight") + if weight < 0 || weight > 100 { + return nil, httperrors.NewInputParameterError("Aliyun instance weight must be in the range of 0 ~ 100") + } + return data, nil +} + +func (self *SAliyunRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + backendgroup, ok := backendGroup.(*models.SLoadbalancerBackendGroup) + if !ok { + return nil, httperrors.NewMissingParameterError("backend_group") + } + if backendgroup.Type != models.LB_BACKENDGROUP_TYPE_NORMAL { + return nil, httperrors.NewInputParameterError("backend group type must be normal") + } + return data, nil +} + +func (self *SAliyunRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + backendgroup, ok := backendGroup.(*models.SLoadbalancerBackendGroup) + if !ok { + return nil, httperrors.NewMissingParameterError("backend_group") + } + listenerType, _ := data.GetString("listener_type") + if utils.IsInStringArray(listenerType, []string{models.LB_LISTENER_TYPE_HTTP, models.LB_LISTENER_TYPE_HTTPS}) && !utils.IsInStringArray(backendgroup.Type, []string{models.LB_BACKENDGROUP_TYPE_DEFAULT, models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE}) { + return nil, httperrors.NewUnsupportOperationError("http or https listener only supportd default or master_slave backendgroup") + } + + lb := backendgroup.GetLoadbalancer() + if tlsCipherPolicy, _ := data.GetString("tls_cipher_policy"); len(tlsCipherPolicy) > 0 && len(lb.LoadbalancerSpec) == 0 { + data.Set("tls_cipher_policy", jsonutils.NewString("")) + } + if healthCheckDomain, _ := data.GetString("health_check_domain"); len(healthCheckDomain) > 80 { + return nil, httperrors.NewInputParameterError("health_check_domain must be in the range of 1 ~ 80") + } + + keyV := map[string]validators.IValidator{ + "bandwidth": validators.NewRangeValidator("bandwidth", 1, 5000), + + "client_request_timeout": validators.NewRangeValidator("client_request_timeout", 1, 180), + + "sticky_session_cookie_timeout": validators.NewRangeValidator("sticky_session_cookie_timeout", 1, 86400), + + "health_check_rise": validators.NewRangeValidator("health_check_rise", 2, 10), + "health_check_fall": validators.NewRangeValidator("health_check_fall", 2, 10), + "health_check_timeout": validators.NewRangeValidator("health_check_timeout", 1, 300), + "health_check_interval": validators.NewRangeValidator("health_check_interval", 1, 50), + } + if !utils.IsInStringArray(listenerType, []string{models.LB_LISTENER_TYPE_UDP, models.LB_LISTENER_TYPE_TCP}) { + keyV["client_idle_timeout"] = validators.NewRangeValidator("client_idle_timeout", 1, 60) + } + + if backendgroup.Type == models.LB_BACKENDGROUP_TYPE_DEFAULT { + keyV["backend_server_port"] = validators.NewPortValidator("backend_server_port") + } + + for _, v := range keyV { + if err := v.Validate(data); err != nil { + return nil, err + } + } + return data, nil +} + +func (self *SAliyunRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + listenerType, _ := data.GetString("listener_type") + + keyV := map[string]validators.IValidator{ + "bandwidth": validators.NewRangeValidator("bandwidth", 1, 5000), + + "client_request_timeout": validators.NewRangeValidator("client_request_timeout", 1, 180), + + "sticky_session_cookie_timeout": validators.NewRangeValidator("sticky_session_cookie_timeout", 1, 86400), + + "health_check_rise": validators.NewRangeValidator("health_check_rise", 2, 10), + "health_check_fall": validators.NewRangeValidator("health_check_fall", 2, 10), + "health_check_timeout": validators.NewRangeValidator("health_check_timeout", 1, 300), + "health_check_interval": validators.NewRangeValidator("health_check_interval", 1, 50), + } + + if healthCheckDomain, _ := data.GetString("health_check_domain"); len(healthCheckDomain) > 80 { + return nil, httperrors.NewInputParameterError("health_check_domain must be in the range of 1 ~ 80") + } + + backendgroup, ok := backendGroup.(*models.SLoadbalancerBackendGroup) + if ok { + if utils.IsInStringArray(listenerType, []string{models.LB_LISTENER_TYPE_HTTP, models.LB_LISTENER_TYPE_HTTPS}) && !utils.IsInStringArray(backendgroup.Type, []string{models.LB_BACKENDGROUP_TYPE_DEFAULT, models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE}) { + return nil, httperrors.NewUnsupportOperationError("http or https listener only supportd default or master_slave backendgroup") + } + + if backendgroup.Type == models.LB_BACKENDGROUP_TYPE_DEFAULT { + keyV["backend_server_port"] = validators.NewPortValidator("backend_server_port") + } + + lb := backendgroup.GetLoadbalancer() + if tlsCipherPolicy, _ := data.GetString("tls_cipher_policy"); len(tlsCipherPolicy) > 0 && len(lb.LoadbalancerSpec) == 0 { + data.Set("tls_cipher_policy", jsonutils.NewString("")) + } + } + + if !utils.IsInStringArray(listenerType, []string{models.LB_LISTENER_TYPE_UDP, models.LB_LISTENER_TYPE_TCP}) { + keyV["client_idle_timeout"] = validators.NewRangeValidator("client_idle_timeout", 1, 60) + } + + for _, v := range keyV { + v.Optional(true) + if err := v.Validate(data); err != nil { + return nil, err + } + } + return data, nil +} diff --git a/pkg/compute/regiondrivers/aws.go b/pkg/compute/regiondrivers/aws.go new file mode 100644 index 0000000000..c56817fb61 --- /dev/null +++ b/pkg/compute/regiondrivers/aws.go @@ -0,0 +1,18 @@ +package regiondrivers + +import ( + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SAwsRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SAwsRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SAwsRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_AWS +} diff --git a/pkg/compute/regiondrivers/azure.go b/pkg/compute/regiondrivers/azure.go new file mode 100644 index 0000000000..e19b1749bc --- /dev/null +++ b/pkg/compute/regiondrivers/azure.go @@ -0,0 +1,18 @@ +package regiondrivers + +import ( + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SAzureRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SAzureRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SAzureRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_AZURE +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go new file mode 100644 index 0000000000..31f299cf06 --- /dev/null +++ b/pkg/compute/regiondrivers/base.go @@ -0,0 +1,102 @@ +package regiondrivers + +import ( + "context" + "fmt" + + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SBaseRegionDriver struct { +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancer") +} + +func (self *SBaseRegionDriver) RequestStartLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestStartLoadbalancer") +} + +func (self *SBaseRegionDriver) RequestStopLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestStopLoadbalancer") +} + +func (self *SBaseRegionDriver) RequestSyncstatusLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestSyncstatusLoadbalancer") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancer") +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancerAcl") +} + +func (self *SBaseRegionDriver) RequestSyncLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestSyncLoadbalancerAcl") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancerAcl") +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *models.SLoadbalancerCertificate, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancerCertificate") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *models.SLoadbalancerCertificate, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancerCertificate") +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancerBackendGroup") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancerBackendGroup") +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancerBackend") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancerBackend") +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancerListener") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancerListener") +} + +func (self *SBaseRegionDriver) RequestStartLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestStartLoadbalancerListener") +} + +func (self *SBaseRegionDriver) RequestStopLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestStopLoadbalancerListener") +} + +func (self *SBaseRegionDriver) RequestSyncstatusLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestSyncstatusLoadbalancerListener") +} + +func (self *SBaseRegionDriver) RequestSyncLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestSyncLoadbalancerListener") +} + +func (self *SBaseRegionDriver) RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateLoadbalancerListenerRule") +} + +func (self *SBaseRegionDriver) RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteLoadbalancerListenerRule") +} diff --git a/pkg/compute/regiondrivers/doc.go b/pkg/compute/regiondrivers/doc.go new file mode 100644 index 0000000000..c574159abd --- /dev/null +++ b/pkg/compute/regiondrivers/doc.go @@ -0,0 +1 @@ +package regiondrivers // import "yunion.io/x/onecloud/pkg/compute/regiondrivers" diff --git a/pkg/compute/regiondrivers/esxi.go b/pkg/compute/regiondrivers/esxi.go new file mode 100644 index 0000000000..010e91c919 --- /dev/null +++ b/pkg/compute/regiondrivers/esxi.go @@ -0,0 +1,18 @@ +package regiondrivers + +import ( + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SEsxiRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SEsxiRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SEsxiRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_VMWARE +} diff --git a/pkg/compute/regiondrivers/huawei.go b/pkg/compute/regiondrivers/huawei.go new file mode 100644 index 0000000000..f54c480666 --- /dev/null +++ b/pkg/compute/regiondrivers/huawei.go @@ -0,0 +1,18 @@ +package regiondrivers + +import ( + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SHuaWeiRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SHuaWeiRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SHuaWeiRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_HUAWEI +} diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go new file mode 100644 index 0000000000..47882154e2 --- /dev/null +++ b/pkg/compute/regiondrivers/kvm.go @@ -0,0 +1,273 @@ +package regiondrivers + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/pkg/utils" +) + +type SKVMRegionDriver struct { + SBaseRegionDriver +} + +func init() { + driver := SKVMRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SKVMRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_KVM +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerAclData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerBackendGroupData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, lb *models.SLoadbalancer, backends []cloudprovider.SLoadbalancerBackend) (*jsonutils.JSONDict, error) { + for _, backend := range backends { + switch backend.BackendType { + case models.LB_BACKEND_GUEST: + if backend.ZoneId != lb.ZoneId { + return nil, fmt.Errorf("zone of host %q (%s) != zone of loadbalancer %q (%s)", + backend.HostName, backend.ZoneId, lb.Name, lb.ZoneId) + } + } + } + return data, nil +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerBackendData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendType string, lb *models.SLoadbalancer, backendGroup *models.SLoadbalancerBackendGroup, backend db.IModel) (*jsonutils.JSONDict, error) { + switch backendType { + case models.LB_BACKEND_GUEST: + guest := backend.(*models.SGuest) + { + // guest zone must match that of loadbalancer's + host := guest.GetHost() + if host == nil { + return nil, fmt.Errorf("error getting host of guest %s", guest.GetId()) + } + + if lb == nil { + return nil, fmt.Errorf("error loadbalancer of backend group %s", backendGroup.GetId()) + } + if host.ZoneId != lb.ZoneId { + return nil, fmt.Errorf("zone of host %q (%s) != zone of loadbalancer %q (%s)", + host.Name, host.ZoneId, lb.Name, lb.ZoneId) + } + } + { + // get guest intranet address + // + // NOTE add address hint (cidr) if needed + address, err := models.LoadbalancerBackendManager.GetGuestAddress(guest) + if err != nil { + return nil, err + } + data.Set("address", jsonutils.NewString(address)) + } + } + return data, nil +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + _, err := models.LoadbalancerManager.TableSpec().Update(lb, func() error { + if lb.AddressType == models.LB_ADDR_TYPE_INTRANET { + // TODO support use reserved ip address + // TODO prefer ip address from server_type loadbalancer? + req := &models.SLoadbalancerNetworkRequestData{ + Loadbalancer: lb, + NetworkId: lb.NetworkId, + Address: lb.Address, + } + // NOTE the small window when agents can see the ephemeral address + ln, err := models.LoadbalancernetworkManager.NewLoadbalancerNetwork(ctx, userCred, req) + if err != nil { + log.Errorf("allocating loadbalancer network failed: %v, req: %#v", err, req) + lb.Address = "" + } else { + lb.Address = ln.IpAddr + } + } + return nil + }) + return nil, err + }) + return nil +} + +func (self *SKVMRegionDriver) RequestStartLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestStopLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestSyncstatusLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + originStatus, _ := task.GetParams().GetString("origin_status") + if utils.IsInStringArray(originStatus, []string{models.LB_STATUS_ENABLED, models.LB_STATUS_DISABLED}) { + lb.SetStatus(userCred, originStatus, "") + } else { + lb.SetStatus(userCred, models.LB_STATUS_ENABLED, "") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestSyncLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *models.SLoadbalancerCertificate, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *models.SLoadbalancerCertificate, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + for _, backend := range backends { + loadbalancerBackend := models.SLoadbalancerBackend{ + BackendGroupId: lbbg.Id, + BackendId: backend.ID, + BackendType: backend.BackendType, + BackendRole: backend.BackendRole, + Weight: backend.Weight, + Address: backend.Address, + Port: backend.Port, + } + loadbalancerBackend.Status = models.LB_STATUS_ENABLED + loadbalancerBackend.ProjectId = userCred.GetProjectId() + loadbalancerBackend.Name = fmt.Sprintf("%s-%s-%s", lbbg.Name, backend.BackendType, backend.Name) + if err := models.LoadbalancerBackendManager.TableSpec().Insert(&loadbalancerBackend); err != nil { + return nil, err + } + } + return nil, nil + }) + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) ValidateDeleteLoadbalancerBackendCondition(ctx context.Context, lbb *models.SLoadbalancerBackend) error { + return nil +} + +func (self *SKVMRegionDriver) ValidateDeleteLoadbalancerBackendGroupCondition(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup) error { + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestStartLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestStopLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestSyncstatusLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + originStatus, _ := task.GetParams().GetString("origin_status") + if utils.IsInStringArray(originStatus, []string{models.LB_STATUS_ENABLED, models.LB_STATUS_DISABLED}) { + lblis.SetStatus(userCred, originStatus, "") + } else { + lblis.SetStatus(userCred, models.LB_STATUS_ENABLED, "") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestSyncLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} + +func (self *SKVMRegionDriver) RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go new file mode 100644 index 0000000000..ab721fa324 --- /dev/null +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -0,0 +1,719 @@ +package regiondrivers + +import ( + "context" + "database/sql" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/pkg/utils" +) + +type SManagedVirtualizationRegionDriver struct { + SVirtualizationRegionDriver +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateManagerId(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + managerID := jsonutils.GetAnyString(data, []string{"manager_id", "manager"}) + if len(managerID) == 0 { + return nil, httperrors.NewMissingParameterError("manager_id") + } + provider, err := models.CloudproviderManager.FetchByIdOrName(userCred, managerID) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("failed to find cloudprovider %s", managerID) + } + return nil, httperrors.NewGeneralError(err) + } + data.Set("manager_id", jsonutils.NewString(provider.GetId())) + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerAclData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return self.ValidateManagerId(ctx, userCred, data) +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateUpdateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerBackendData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendType string, lb *models.SLoadbalancer, backendGroup *models.SLoadbalancerBackendGroup, backend db.IModel) (*jsonutils.JSONDict, error) { + if backendType != models.LB_BACKEND_GUEST { + return nil, httperrors.NewUnsupportOperationError("internal error: unexpected backend type %s", backendType) + } + guest := backend.(*models.SGuest) + host := guest.GetHost() + if host == nil { + return nil, fmt.Errorf("error getting host of guest %s", guest.GetId()) + } + if lb == nil { + return nil, fmt.Errorf("error loadbalancer of backend group %s", backendGroup.GetId()) + } + hostRegion := host.GetRegion() + lbRegion := lb.GetRegion() + if hostRegion.Id != lbRegion.Id { + return nil, httperrors.NewInputParameterError("region of host %q (%s) != region of loadbalancer %q (%s))", + host.Name, host.ZoneId, lb.Name, lb.ZoneId) + } + address, err := models.LoadbalancerBackendManager.GetGuestAddress(guest) + if err != nil { + return nil, err + } + data.Set("address", jsonutils.NewString(address)) + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerBackendGroupData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, lb *models.SLoadbalancer, backends []cloudprovider.SLoadbalancerBackend) (*jsonutils.JSONDict, error) { + for _, backend := range backends { + if len(backend.ExternalID) == 0 { + return nil, httperrors.NewInputParameterError("invalid guest %s", backend.Name) + } + } + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + return data, nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateDeleteLoadbalancerBackendCondition(ctx context.Context, lbb *models.SLoadbalancerBackend) error { + return nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateDeleteLoadbalancerBackendGroupCondition(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup) error { + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + params, err := lb.GetCreateLoadbalancerParams(iRegion) + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.CreateILoadBalancer(params) + if err != nil { + return nil, err + } + if err := lb.SetExternalId(iLoadbalancer.GetGlobalId()); err != nil { + return nil, err + } + if err := lb.SyncWithCloudLoadbalancer(ctx, userCred, iLoadbalancer, "", false); err != nil { + return nil, err + } + lbbgs, err := iLoadbalancer.GetILoadBalancerBackendGroups() + if err != nil { + return nil, err + } + if len(lbbgs) > 0 { + provider := lb.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find cloudprovider for lb %s", lb.Name) + } + models.LoadbalancerBackendGroupManager.SyncLoadbalancerBackendgroups(ctx, userCred, provider, lb, lbbgs, &models.SSyncRange{}) + } + return nil, nil + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestStartLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + return nil, iLoadbalancer.Start() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestStopLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + return nil, iLoadbalancer.Stop() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestSyncstatusLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + status := iLoadbalancer.GetStatus() + if utils.IsInStringArray(status, []string{models.LB_STATUS_ENABLED, models.LB_STATUS_DISABLED}) { + return nil, lb.SetStatus(userCred, status, "") + } + return nil, fmt.Errorf("Unknown loadbalancer status %s", status) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + return nil, iLoadbalancer.Delete() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lbacl.GetIRegion() + if err != nil { + return nil, err + } + acl := &cloudprovider.SLoadbalancerAccessControlList{Name: lbacl.Name, Entrys: []cloudprovider.SLoadbalancerAccessControlListEntry{}} + if lbacl.AclEntries != nil { + for _, entry := range *lbacl.AclEntries { + acl.Entrys = append(acl.Entrys, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: entry.Cidr, Comment: entry.Comment}) + } + } + iLoadbalancerAcl, err := iRegion.CreateILoadBalancerAcl(acl) + if err != nil { + return nil, err + } + if err := lbacl.SetExternalId(iLoadbalancerAcl.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbacl.SyncWithCloudLoadbalancerAcl(ctx, userCred, iLoadbalancerAcl, "", false) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestSyncLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lbacl.GetIRegion() + if err != nil { + return nil, err + } + acl := &cloudprovider.SLoadbalancerAccessControlList{Name: lbacl.Name, Entrys: []cloudprovider.SLoadbalancerAccessControlListEntry{}} + if lbacl.AclEntries != nil { + for _, entry := range *lbacl.AclEntries { + acl.Entrys = append(acl.Entrys, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: entry.Cidr, Comment: entry.Comment}) + } + } + lockman.LockRawObject(ctx, "acl", lbacl.Id) + defer lockman.ReleaseRawObject(ctx, "acl", lbacl.Id) + + iLoadbalancerAcl, err := iRegion.GetILoadBalancerAclById(lbacl.ExternalId) + if err != nil { + return nil, err + } + return nil, iLoadbalancerAcl.Sync(acl) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SLoadbalancerAcl, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + iRegion, err := lbacl.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancerAcl, err := iRegion.GetILoadBalancerAclById(lbacl.ExternalId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + return nil, iLoadbalancerAcl.Delete() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *models.SLoadbalancerCertificate, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lbcert.GetIRegion() + if err != nil { + return nil, err + } + certificate := &cloudprovider.SLoadbalancerCertificate{ + Name: lbcert.Name, + PrivateKey: lbcert.PrivateKey, + Certificate: lbcert.Certificate, + } + iLoadbalancerCert, err := iRegion.CreateILoadBalancerCertificate(certificate) + if err != nil { + return nil, err + } + if err := lbcert.SetExternalId(iLoadbalancerCert.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbcert.SyncWithCloudLoadbalancerCertificate(ctx, userCred, iLoadbalancerCert, "", false) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancerCertificate(ctx context.Context, userCred mcclient.TokenCredential, lbcert *models.SLoadbalancerCertificate, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + iRegion, err := lbcert.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancerCert, err := iRegion.GetILoadBalancerCertificateById(lbcert.ExternalId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + return nil, iLoadbalancerCert.Delete() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lbbg.GetIRegion() + if err != nil { + return nil, err + } + loadbalancer := lbbg.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + group := &cloudprovider.SLoadbalancerBackendGroup{ + Name: lbbg.Name, + GroupType: lbbg.Type, + Backends: backends, + } + iLoadbalancerBackendGroup, err := iLoadbalancer.CreateILoadBalancerBackendGroup(group) + if err != nil { + return nil, err + } + if err := lbbg.SetExternalId(iLoadbalancerBackendGroup.GetGlobalId()); err != nil { + return nil, err + } + iBackends, err := iLoadbalancerBackendGroup.GetILoadbalancerBackends() + if err != nil { + return nil, err + } + if len(iBackends) > 0 { + provider := loadbalancer.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find cloudprovider for lb %s", loadbalancer.Name) + } + models.LoadbalancerBackendManager.SyncLoadbalancerBackends(ctx, userCred, provider, lbbg, iBackends, &models.SSyncRange{}) + } + return nil, nil + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + iRegion, err := lbbg.GetIRegion() + if err != nil { + return nil, err + } + loadbalancer := lbbg.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(lbbg.ExternalId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + return nil, iLoadbalancerBackendGroup.Delete() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + lbbg := lbb.GetLoadbalancerBackendGroup() + if lbbg == nil { + return nil, fmt.Errorf("failed to find lbbg for backend %s", lbb.Name) + } + lb := lbbg.GetLoadbalancer() + if lb == nil { + return nil, fmt.Errorf("failed to find lb for backendgroup %s", lbbg.Name) + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(lbbg.ExternalId) + if err != nil { + return nil, err + } + guest := lbb.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find guest for lbb %s", lbb.Name) + } + iLoadbalancerBackend, err := iLoadbalancerBackendGroup.AddBackendServer(guest.ExternalId, lbb.Weight, lbb.Port) + if err != nil { + return nil, err + } + if err := lbb.SetExternalId(iLoadbalancerBackend.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbb.SyncWithCloudLoadbalancerBackend(ctx, userCred, iLoadbalancerBackend, "", false) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + lbbg := lbb.GetLoadbalancerBackendGroup() + if lbbg == nil { + return nil, fmt.Errorf("failed to find lbbg for backend %s", lbb.Name) + } + lb := lbbg.GetLoadbalancer() + if lb == nil { + return nil, fmt.Errorf("failed to find lb for backendgroup %s", lbbg.Name) + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(lbbg.ExternalId) + if err != nil { + return nil, err + } + guest := lbb.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find guest for lbb %s", lbb.Name) + } + return nil, iLoadbalancerBackendGroup.RemoveBackendServer(guest.ExternalId, lbb.Weight, lbb.Port) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + params, err := lblis.GetLoadbalancerListenerParams() + if err != nil { + return nil, err + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.CreateILoadBalancerListener(params) + if err != nil { + return nil, err + } + if err := lblis.SetExternalId(iListener.GetGlobalId()); err != nil { + return nil, err + } + return nil, lblis.SyncWithCloudLoadbalancerListener(ctx, userCred, loadbalancer, iListener, "", false) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + return nil, iListener.Delete() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestStartLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + return nil, err + } + return nil, iListener.Start() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestSyncLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + params, err := lblis.GetLoadbalancerListenerParams() + if err != nil { + return nil, err + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + return nil, err + } + if err := iListener.Sync(params); err != nil { + return nil, err + } + if err := iListener.Refresh(); err != nil { + return nil, err + } + return nil, lblis.SyncWithCloudLoadbalancerListener(ctx, userCred, loadbalancer, iListener, "", false) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestStopLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + return nil, err + } + return nil, iListener.Stop() + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestSyncstatusLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + return nil, err + } + status := iListener.GetStatus() + if utils.IsInStringArray(status, []string{models.LB_STATUS_ENABLED, models.LB_STATUS_DISABLED}) { + return nil, lblis.SetStatus(userCred, status, "") + } + return nil, fmt.Errorf("Unknown loadbalancer listener status %s", status) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + listener := lbr.GetLoadbalancerListener() + if listener == nil { + return nil, fmt.Errorf("failed to find listener for listnener rule %s", lbr.Name) + } + loadbalancer := listener.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for listener %s", listener.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(listener.ExternalId) + if err != nil { + return nil, err + } + rule := &cloudprovider.SLoadbalancerListenerRule{ + Name: lbr.Name, + Domain: lbr.Domain, + Path: lbr.Path, + } + if len(lbr.BackendGroupId) > 0 { + group := lbr.GetLoadbalancerBackendGroup() + if group == nil { + return nil, fmt.Errorf("failed to find backend group for listener rule %s", lbr.Name) + } + rule.BackendGroupID = group.ExternalId + rule.BackendGroupType = group.Type + } + iListenerRule, err := iListener.CreateILoadBalancerListenerRule(rule) + if err != nil { + return nil, err + } + if err := lbr.SetExternalId(iListenerRule.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbr.SyncWithCloudLoadbalancerListenerRule(ctx, userCred, iListenerRule, "", false) + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + listener := lbr.GetLoadbalancerListener() + if listener == nil { + return nil, fmt.Errorf("failed to find listener for listnener rule %s", lbr.Name) + } + loadbalancer := listener.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for listener %s", listener.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(listener.ExternalId) + if err != nil { + return nil, err + } + iListenerRule, err := iListener.GetILoadBalancerListenerRuleById(lbr.ExternalId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + return nil, iListenerRule.Delete() + }) + return nil +} diff --git a/pkg/compute/regiondrivers/openstack.go b/pkg/compute/regiondrivers/openstack.go new file mode 100644 index 0000000000..0f6db5e04e --- /dev/null +++ b/pkg/compute/regiondrivers/openstack.go @@ -0,0 +1,18 @@ +package regiondrivers + +import ( + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SOpenStackRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SOpenStackRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SOpenStackRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_OPENSTACK +} diff --git a/pkg/compute/regiondrivers/qcloud.go b/pkg/compute/regiondrivers/qcloud.go new file mode 100644 index 0000000000..6aed19e9be --- /dev/null +++ b/pkg/compute/regiondrivers/qcloud.go @@ -0,0 +1,18 @@ +package regiondrivers + +import ( + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SQcloudRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SQcloudRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SQcloudRegionDriver) GetProvider() string { + return models.CLOUD_PROVIDER_QCLOUD +} diff --git a/pkg/compute/regiondrivers/virtualization.go b/pkg/compute/regiondrivers/virtualization.go new file mode 100644 index 0000000000..75afea6d53 --- /dev/null +++ b/pkg/compute/regiondrivers/virtualization.go @@ -0,0 +1,5 @@ +package regiondrivers + +type SVirtualizationRegionDriver struct { + SBaseRegionDriver +} diff --git a/pkg/compute/service/influxdb.go b/pkg/compute/service/influxdb.go new file mode 100644 index 0000000000..c1fab352b4 --- /dev/null +++ b/pkg/compute/service/influxdb.go @@ -0,0 +1,42 @@ +package service + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/influxdb" +) + +func setInfluxdbRetentionPolicy() error { + urls, err := auth.GetServiceURLs("influxdb", options.Options.Region, "", "internal") + if err != nil { + return err + } + for _, url := range urls { + err = setInfluxdbRetentionPolicyForUrl(url) + if err != nil { + return err + } + } + return nil +} + +func setInfluxdbRetentionPolicyForUrl(url string) error { + db := influxdb.NewInfluxdb(url) + err := db.SetDatabase("telegraf") + if err != nil { + return err + } + rp := influxdb.SRetentionPolicy{ + Name: "30day_only", + Duration: fmt.Sprintf("%dd", options.Options.MetricsRetentionDays), + ReplicaN: 1, + Default: true, + } + err = db.SetRetentionPolicy(rp) + if err != nil { + return err + } + return nil +} diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index 048f5ef7ed..3e5e30273f 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -10,6 +10,7 @@ import ( _ "yunion.io/x/onecloud/pkg/compute/guestdrivers" _ "yunion.io/x/onecloud/pkg/compute/hostdrivers" + _ "yunion.io/x/onecloud/pkg/compute/regiondrivers" _ "yunion.io/x/onecloud/pkg/compute/tasks" _ "yunion.io/x/onecloud/pkg/util/aliyun/provider" _ "yunion.io/x/onecloud/pkg/util/aws/provider" @@ -59,7 +60,12 @@ func StartService() { log.Errorf("InitDB fail: %s", err) } - cron := cronman.GetCronJobManager() + err = setInfluxdbRetentionPolicy() + if err != nil { + log.Errorf("setInfluxdbRetentionPolicy fail: %s", err) + } + + cron := cronman.GetCronJobManager(true) cron.AddJob1("CleanPendingDeleteServers", time.Duration(opts.PendingDeleteCheckSeconds)*time.Second, models.GuestManager.CleanPendingDeleteServers) cron.AddJob1("CleanPendingDeleteDisks", time.Duration(opts.PendingDeleteCheckSeconds)*time.Second, models.DiskManager.CleanPendingDeleteDisks) cron.AddJob1("CleanPendingDeleteLoadbalancers", time.Duration(opts.LoadbalancerPendingDeleteCheckInterval)*time.Second, models.LoadbalancerAgentManager.CleanPendingDeleteLoadbalancers) diff --git a/pkg/compute/tasks/baremetal_server_rebuild_root_task.go b/pkg/compute/tasks/baremetal_server_rebuild_root_task.go new file mode 100644 index 0000000000..16ed55ddb8 --- /dev/null +++ b/pkg/compute/tasks/baremetal_server_rebuild_root_task.go @@ -0,0 +1,83 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "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 BaremetalServerRebuildRootTask struct { + SGuestBaseTask +} + +func init() { + taskman.RegisterTask(BaremetalServerRebuildRootTask{}) +} + +func (self *BaremetalServerRebuildRootTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + if jsonutils.QueryBoolean(self.Params, "need_stop", false) { + self.SetStage("OnStopServerComplete", nil) + guest.StartGuestStopTask(ctx, self.UserCred, false, self.GetTaskId()) + return + } + self.StartRebuildRootDisk(ctx, guest) +} + +func (self *BaremetalServerRebuildRootTask) OnStopServerComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.StartRebuildRootDisk(ctx, guest) +} + +func (self *BaremetalServerRebuildRootTask) StartRebuildRootDisk(ctx context.Context, guest *models.SGuest) { + if guest.Status != models.VM_ADMIN { + guest.SetStatus(self.UserCred, models.VM_REBUILD_ROOT, "") + } + imageId, _ := self.Params.GetString("image_id") + db.OpsLog.LogEvent(guest, db.ACT_REBUILDING_ROOT, imageId, self.UserCred) + gds := guest.CategorizeDisks() + oldStatus := gds.Root.Status + _, err := gds.Root.GetModelManager().TableSpec().Update(gds.Root, func() error { + gds.Root.TemplateId = imageId + gds.Root.Status = models.DISK_REBUILD + return nil + }) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + logclient.AddActionLog(guest, logclient.ACT_VM_REBUILD, err, self.UserCred, false) + return + } else { + db.OpsLog.LogEvent(gds.Root, db.ACT_UPDATE_STATUS, + fmt.Sprintf("%s=>%s", oldStatus, models.DISK_REBUILD), self.UserCred) + } + self.SetStage("OnRebuildRootDiskComplete", nil) + + // clear logininfo + loginParams := make(map[string]interface{}) + loginParams["login_account"] = "none" + loginParams["login_key"] = "none" + loginParams["login_key_timestamp"] = "none" + guest.SetAllMetadata(ctx, loginParams, self.UserCred) + guest.StartGuestDeployTask(ctx, self.UserCred, self.Params, "rebuild", self.GetTaskId()) +} + +func (self *BaremetalServerRebuildRootTask) OnRebuildRootDiskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(guest, db.ACT_REBUILD_ROOT, "", self.UserCred) + self.SetStage("OnSyncStatusComplete", nil) + guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) +} + +func (self *BaremetalServerRebuildRootTask) OnRebuildRootDiskCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(guest, db.ACT_REBUILD_ROOT_FAIL, data, self.UserCred) + if guest.Status != models.VM_ADMIN { + guest.SetStatus(self.UserCred, models.VM_REBUILD_ROOT_FAIL, "") + } +} + +func (self *BaremetalServerRebuildRootTask) OnSyncStatusComplete(ctx context.Context, _ *models.SGuest, _ jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/cloud_account_sync_task.go b/pkg/compute/tasks/cloud_account_sync_task.go index 6806cf5508..5869316d9d 100644 --- a/pkg/compute/tasks/cloud_account_sync_task.go +++ b/pkg/compute/tasks/cloud_account_sync_task.go @@ -33,15 +33,13 @@ func (self *CloudAccountSyncInfoTask) OnInit(ctx context.Context, objs []db.ISta self.SetStageFailed(ctx, "cloudprovide fail to get valid cloudaccount") return } - account.MarkStartSync(self.UserCred) } else { - account.SetStatus(self.UserCred, models.CLOUD_PROVIDER_CONNECTED, "") - self.SetStageComplete(ctx, nil) + self.SetStageFailed(ctx, "no cloudprovider for sync") return } if _, err := account.GetSubAccounts(); err != nil { - account.SetStatus(self.UserCred, models.CLOUD_PROVIDER_DISCONNECTED, "") + account.SetStatus(self.UserCred, models.CLOUD_PROVIDER_DISCONNECTED, err.Error()) self.SetStageFailed(ctx, err.Error()) return } diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index 43b837d5f5..06da537221 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/utils" @@ -15,7 +16,9 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/skus" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/pkg/util/compare" ) type CloudProviderSyncInfoTask struct { @@ -108,6 +111,25 @@ func syncCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, } } +type sStoragecacheSyncPair struct { + local *models.SStoragecache + remote cloudprovider.ICloudStoragecache +} + +func (pair *sStoragecacheSyncPair) syncCloudImages(ctx context.Context, userCred mcclient.TokenCredential) compare.SyncResult { + return pair.local.SyncCloudImages(ctx, userCred, pair.remote) +} + +func isInCache(pairs []sStoragecacheSyncPair, localCacheId string) bool { + log.Debugf("isInCache %d %s", len(pairs), localCacheId) + for i := range pairs { + if pairs[i].local.Id == localCacheId { + return true + } + } + return false +} + func syncPublicCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, syncRange *models.SSyncRange) { regions := driver.GetIRegions() @@ -125,6 +147,8 @@ func syncPublicCloudProviderInfo(ctx context.Context, provider *models.SCloudpro return } + storageCachePairs := make([]sStoragecacheSyncPair, 0) + db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) logclient.AddActionLog(provider, getAction(task.Params), "", task.UserCred, true) for i := 0; i < len(localRegions); i += 1 { @@ -136,6 +160,7 @@ func syncPublicCloudProviderInfo(ctx context.Context, provider *models.SCloudpro localZones, remoteZones := syncRegionZones(ctx, provider, task, &localRegions[i], remoteRegions[i]) syncRegionSkus(ctx, provider, task, &localRegions[i]) + syncRegionVPCs(ctx, provider, task, &localRegions[i], remoteRegions[i], syncRange) if localZones != nil && remoteZones != nil { @@ -144,21 +169,40 @@ func syncPublicCloudProviderInfo(ctx context.Context, provider *models.SCloudpro if len(syncRange.Zone) > 0 && !utils.IsInStringArray(localZones[j].Id, syncRange.Zone) { continue } - syncZoneStorages(ctx, provider, task, &localZones[j], remoteZones[j], syncRange) - syncZoneHosts(ctx, provider, task, &localZones[j], remoteZones[j], syncRange) + newPairs := syncZoneStorages(ctx, provider, task, driver, &localZones[j], remoteZones[j], syncRange, storageCachePairs) + if len(newPairs) > 0 { + storageCachePairs = append(storageCachePairs, newPairs...) + } + newPairs = syncZoneHosts(ctx, provider, task, driver, &localZones[j], remoteZones[j], syncRange, storageCachePairs) + if len(newPairs) > 0 { + storageCachePairs = append(storageCachePairs, newPairs...) + } } } syncRegionSnapshots(ctx, provider, task, &localRegions[i], remoteRegions[i], syncRange) + syncRegionLoadbalancerAcls(ctx, provider, task, &localRegions[i], remoteRegions[i], syncRange) syncRegionLoadbalancerCertificates(ctx, provider, task, &localRegions[i], remoteRegions[i], syncRange) syncRegionLoadbalancers(ctx, provider, task, &localRegions[i], remoteRegions[i], syncRange) } + + log.Debugf("storageCachePairs count %d", len(storageCachePairs)) + for i := range storageCachePairs { + result := storageCachePairs[i].syncCloudImages(ctx, task.GetUserCred()) + msg := result.Result() + log.Infof("syncCloudImages result: %s", msg) + // skip errors + // if result.IsError() { + // logSyncFailed(provider, task, msg) + // return + // } + } } func syncRegionLoadbalancerCertificates(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localRegion *models.SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *models.SSyncRange) { - certificates, err := remoteRegion.GetILoadbalancerCertificates() + certificates, err := remoteRegion.GetILoadBalancerCertificates() if err != nil { - msg := fmt.Sprintf("GetILoadbalancerCertificates for region %s failed %s", remoteRegion.GetName(), err) + msg := fmt.Sprintf("GetILoadBalancerCertificates for region %s failed %s", remoteRegion.GetName(), err) log.Errorf(msg) logSyncFailed(provider, task, msg) return @@ -173,9 +217,9 @@ func syncRegionLoadbalancerCertificates(ctx context.Context, provider *models.SC } func syncRegionLoadbalancerAcls(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localRegion *models.SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *models.SSyncRange) { - acls, err := remoteRegion.GetILoadbalancerAcls() + acls, err := remoteRegion.GetILoadBalancerAcls() if err != nil { - msg := fmt.Sprintf("GetILoadbalancerAcls for region %s failed %s", remoteRegion.GetName(), err) + msg := fmt.Sprintf("GetILoadBalancerAcls for region %s failed %s", remoteRegion.GetName(), err) log.Errorf(msg) logSyncFailed(provider, task, msg) return @@ -212,9 +256,9 @@ func syncRegionLoadbalancers(ctx context.Context, provider *models.SCloudprovide } func syncLoadbalancerListeners(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localLoadbalancer *models.SLoadbalancer, remoteLoadbalancer cloudprovider.ICloudLoadbalancer, syncRange *models.SSyncRange) { - remoteListeners, err := remoteLoadbalancer.GetILoadbalancerListeners() + remoteListeners, err := remoteLoadbalancer.GetILoadBalancerListeners() if err != nil { - msg := fmt.Sprintf("GetILoadbalancerListeners for loadbalancer %s failed %s", localLoadbalancer.Name, err) + msg := fmt.Sprintf("GetILoadBalancerListeners for loadbalancer %s failed %s", localLoadbalancer.Name, err) log.Errorf(msg) logSyncFailed(provider, task, msg) return @@ -249,9 +293,9 @@ func syncLoadbalancerListenerRules(ctx context.Context, provider *models.SCloudp } func syncLoadbalancerBackendgroups(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localLoadbalancer *models.SLoadbalancer, remoteLoadbalancer cloudprovider.ICloudLoadbalancer, syncRange *models.SSyncRange) { - remoteBackendgroups, err := remoteLoadbalancer.GetILoadbalancerBackendGroups() + remoteBackendgroups, err := remoteLoadbalancer.GetILoadBalancerBackendGroups() if err != nil { - msg := fmt.Sprintf("GetILoadbalancerBackendGroups for loadbalancer %s failed %s", localLoadbalancer.Name, err) + msg := fmt.Sprintf("GetILoadBalancerBackendGroups for loadbalancer %s failed %s", localLoadbalancer.Name, err) log.Errorf(msg) logSyncFailed(provider, task, msg) return @@ -468,13 +512,13 @@ func syncWireNetworks(ctx context.Context, provider *models.SCloudprovider, task logclient.AddActionLog(provider, getAction(task.GetParams()), notes, task.GetUserCred(), true) } -func syncZoneStorages(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localZone *models.SZone, remoteZone cloudprovider.ICloudZone, syncRange *models.SSyncRange) { +func syncZoneStorages(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, localZone *models.SZone, remoteZone cloudprovider.ICloudZone, syncRange *models.SSyncRange, storageCachePairs []sStoragecacheSyncPair) []sStoragecacheSyncPair { storages, err := remoteZone.GetIStorages() if err != nil { msg := fmt.Sprintf("GetIStorages for zone %s failed %s", remoteZone.GetName(), err) log.Errorf(msg) logSyncFailed(provider, task, msg) - return + return nil } localStorages, remoteStorages, result := models.StorageManager.SyncStorages(ctx, task.UserCred, provider, localZone, storages) msg := result.Result() @@ -482,18 +526,25 @@ func syncZoneStorages(ctx context.Context, provider *models.SCloudprovider, task log.Infof(notes) if result.IsError() { logSyncFailed(provider, task, msg) - return + return nil } db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) logclient.AddActionLog(provider, getAction(task.GetParams()), notes, task.GetUserCred(), true) + newCacheIds := make([]sStoragecacheSyncPair, 0) for i := 0; i < len(localStorages); i += 1 { - syncStorageCaches(ctx, provider, task, &localStorages[i], remoteStorages[i]) - syncStorageDisks(ctx, provider, task, &localStorages[i], remoteStorages[i], syncRange) + if !isInCache(storageCachePairs, localStorages[i].StoragecacheId) && !isInCache(newCacheIds, localStorages[i].StoragecacheId) { + cachePair := syncStorageCaches(ctx, provider, task, &localStorages[i], remoteStorages[i]) + if cachePair.remote != nil && cachePair.local != nil { + newCacheIds = append(newCacheIds, cachePair) + } + } + syncStorageDisks(ctx, provider, task, driver, &localStorages[i], remoteStorages[i], syncRange) } + return newCacheIds } -func syncStorageCaches(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localStorage *models.SStorage, remoteStorage cloudprovider.ICloudStorage) { +func syncStorageCaches(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localStorage *models.SStorage, remoteStorage cloudprovider.ICloudStorage) (cachePair sStoragecacheSyncPair) { remoteCache := remoteStorage.GetIStoragecache() localCache, err := models.StoragecacheManager.SyncWithCloudStoragecache(remoteCache) if err != nil { @@ -508,9 +559,12 @@ func syncStorageCaches(ctx context.Context, provider *models.SCloudprovider, tas log.Errorf(msg) logSyncFailed(provider, task, msg) } + cachePair.local = localCache + cachePair.remote = remoteCache + return } -func syncStorageDisks(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localStorage *models.SStorage, remoteStorage cloudprovider.ICloudStorage, syncRange *models.SSyncRange) { +func syncStorageDisks(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, localStorage *models.SStorage, remoteStorage cloudprovider.ICloudStorage, syncRange *models.SSyncRange) { disks, err := remoteStorage.GetIDisks() if err != nil { msg := fmt.Sprintf("GetIDisks for storage %s failed %s", remoteStorage.GetName(), err) @@ -518,7 +572,7 @@ func syncStorageDisks(ctx context.Context, provider *models.SCloudprovider, task logSyncFailed(provider, task, msg) return } - _, _, result := models.DiskManager.SyncDisks(ctx, task.UserCred, localStorage, disks, provider.ProjectId, syncRange.ProjectSync) + _, _, result := models.DiskManager.SyncDisks(ctx, task.UserCred, driver, localStorage, disks, provider.ProjectId, syncRange.ProjectSync) msg := result.Result() notes := fmt.Sprintf("SyncDisks for storage %s result: %s", localStorage.Name, msg) log.Infof(notes) @@ -530,13 +584,13 @@ func syncStorageDisks(ctx context.Context, provider *models.SCloudprovider, task logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) } -func syncZoneHosts(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localZone *models.SZone, remoteZone cloudprovider.ICloudZone, syncRange *models.SSyncRange) { +func syncZoneHosts(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, localZone *models.SZone, remoteZone cloudprovider.ICloudZone, syncRange *models.SSyncRange, storageCachePairs []sStoragecacheSyncPair) []sStoragecacheSyncPair { hosts, err := remoteZone.GetIHosts() if err != nil { msg := fmt.Sprintf("GetIHosts for zone %s failed %s", remoteZone.GetName(), err) log.Errorf(msg) logSyncFailed(provider, task, msg) - return + return nil } localHosts, remoteHosts, result := models.HostManager.SyncHosts(ctx, task.UserCred, provider, localZone, hosts, syncRange.ProjectSync) msg := result.Result() @@ -544,27 +598,29 @@ func syncZoneHosts(ctx context.Context, provider *models.SCloudprovider, task *C log.Infof(notes) if result.IsError() { logSyncFailed(provider, task, msg) - return + return nil } + var newCachePairs []sStoragecacheSyncPair db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) for i := 0; i < len(localHosts); i += 1 { if len(syncRange.Host) > 0 && !utils.IsInStringArray(localHosts[i].Id, syncRange.Host) { continue } - syncHostStorages(ctx, provider, task, &localHosts[i], remoteHosts[i]) + newCachePairs = syncHostStorages(ctx, provider, task, &localHosts[i], remoteHosts[i], storageCachePairs) syncHostWires(ctx, provider, task, &localHosts[i], remoteHosts[i]) - syncHostVMs(ctx, provider, task, &localHosts[i], remoteHosts[i], syncRange) + syncHostVMs(ctx, provider, task, driver, &localHosts[i], remoteHosts[i], syncRange) } + return newCachePairs } -func syncHostStorages(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localHost *models.SHost, remoteHost cloudprovider.ICloudHost) { +func syncHostStorages(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localHost *models.SHost, remoteHost cloudprovider.ICloudHost, storageCachePairs []sStoragecacheSyncPair) []sStoragecacheSyncPair { storages, err := remoteHost.GetIStorages() if err != nil { msg := fmt.Sprintf("GetIStorages for host %s failed %s", remoteHost.GetName(), err) log.Errorf(msg) logSyncFailed(provider, task, msg) - return + return nil } localStorages, remoteStorages, result := localHost.SyncHostStorages(ctx, task.UserCred, storages) msg := result.Result() @@ -572,14 +628,21 @@ func syncHostStorages(ctx context.Context, provider *models.SCloudprovider, task log.Infof(notes) if result.IsError() { logSyncFailed(provider, task, msg) - return + return nil } db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) + newCacheIds := make([]sStoragecacheSyncPair, 0) for i := 0; i < len(localStorages); i += 1 { - syncStorageCaches(ctx, provider, task, &localStorages[i], remoteStorages[i]) + if !isInCache(storageCachePairs, localStorages[i].StoragecacheId) && !isInCache(newCacheIds, localStorages[i].StoragecacheId) { + cachePair := syncStorageCaches(ctx, provider, task, &localStorages[i], remoteStorages[i]) + if cachePair.remote != nil && cachePair.local != nil { + newCacheIds = append(newCacheIds, cachePair) + } + } } + return newCacheIds } func syncHostWires(ctx context.Context, provider *models.SCloudprovider, task taskman.ITask, localHost *models.SHost, remoteHost cloudprovider.ICloudHost) { @@ -602,7 +665,7 @@ func syncHostWires(ctx context.Context, provider *models.SCloudprovider, task ta logclient.AddActionLog(provider, getAction(task.GetParams()), notes, task.GetUserCred(), true) } -func syncHostVMs(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localHost *models.SHost, remoteHost cloudprovider.ICloudHost, syncRange *models.SSyncRange) { +func syncHostVMs(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, localHost *models.SHost, remoteHost cloudprovider.ICloudHost, syncRange *models.SSyncRange) { vms, err := remoteHost.GetIVMs() if err != nil { msg := fmt.Sprintf("GetIVMs for host %s failed %s", remoteHost.GetName(), err) @@ -610,7 +673,7 @@ func syncHostVMs(ctx context.Context, provider *models.SCloudprovider, task *Clo logSyncFailed(provider, task, msg) return } - localVMs, remoteVMs, result := localHost.SyncHostVMs(ctx, task.UserCred, vms, provider.ProjectId, syncRange.ProjectSync) + localVMs, remoteVMs, result := localHost.SyncHostVMs(ctx, task.UserCred, driver, vms, provider.ProjectId, syncRange.ProjectSync) msg := result.Result() notes := fmt.Sprintf("SyncHostVMs for host %s result: %s", localHost.Name, msg) log.Infof(notes) @@ -622,7 +685,7 @@ func syncHostVMs(ctx context.Context, provider *models.SCloudprovider, task *Clo logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) for i := 0; i < len(localVMs); i += 1 { syncVMNics(ctx, provider, task, localHost, &localVMs[i], remoteVMs[i]) - syncVMDisks(ctx, provider, task, localHost, &localVMs[i], remoteVMs[i], syncRange) + syncVMDisks(ctx, provider, task, driver, localHost, &localVMs[i], remoteVMs[i], syncRange) syncVMEip(ctx, provider, task, &localVMs[i], remoteVMs[i]) if localVMs[i].Status == models.VM_RUNNING { @@ -651,7 +714,7 @@ func syncVMNics(ctx context.Context, provider *models.SCloudprovider, task *Clou logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) } -func syncVMDisks(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, host *models.SHost, localVM *models.SGuest, remoteVM cloudprovider.ICloudVM, syncRange *models.SSyncRange) { +func syncVMDisks(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, host *models.SHost, localVM *models.SGuest, remoteVM cloudprovider.ICloudVM, syncRange *models.SSyncRange) { disks, err := remoteVM.GetIDisks() if err != nil { msg := fmt.Sprintf("GetIDisks for VM %s failed %s", remoteVM.GetName(), err) @@ -659,7 +722,7 @@ func syncVMDisks(ctx context.Context, provider *models.SCloudprovider, task *Clo logSyncFailed(provider, task, msg) return } - result := localVM.SyncVMDisks(ctx, task.UserCred, host, disks, provider.ProjectId, syncRange.ProjectSync) + result := localVM.SyncVMDisks(ctx, task.UserCred, driver, host, disks, provider.ProjectId, syncRange.ProjectSync) msg := result.Result() notes := fmt.Sprintf("syncVMDisks for VM %s result: %s", localVM.Name, msg) log.Infof(notes) diff --git a/pkg/compute/tasks/cloud_provider_sync_onpremise_info_task.go b/pkg/compute/tasks/cloud_provider_sync_onpremise_info_task.go index 6c6761db62..8262744b55 100644 --- a/pkg/compute/tasks/cloud_provider_sync_onpremise_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_onpremise_info_task.go @@ -41,13 +41,29 @@ func syncOnPremiseCloudProviderInfo(ctx context.Context, provider *models.SCloud db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) + storageCachePairs := make([]sStoragecacheSyncPair, 0) + for i := 0; i < len(localHosts); i += 1 { if len(syncRange.Host) > 0 && !utils.IsInStringArray(localHosts[i].Id, syncRange.Host) { continue } - syncHostStorages(ctx, provider, task, &localHosts[i], remoteHosts[i]) + newCachePairs := syncHostStorages(ctx, provider, task, &localHosts[i], remoteHosts[i], storageCachePairs) + if len(newCachePairs) > 0 { + storageCachePairs = append(storageCachePairs, newCachePairs...) + } syncHostNics(ctx, provider, task, &localHosts[i], remoteHosts[i]) - syncHostVMs(ctx, provider, task, &localHosts[i], remoteHosts[i], syncRange) + syncHostVMs(ctx, provider, task, driver, &localHosts[i], remoteHosts[i], syncRange) + } + + log.Debugf("storageCachePairs count %d", len(storageCachePairs)) + for i := range storageCachePairs { + result := storageCachePairs[i].syncCloudImages(ctx, task.GetUserCred()) + msg := result.Result() + log.Infof("syncCloudImages result: %s", msg) + if result.IsError() { + logSyncFailed(provider, task, msg) + return + } } } diff --git a/pkg/compute/tasks/disk_resize_task.go b/pkg/compute/tasks/disk_resize_task.go index 32f6d102b2..022e1312a5 100644 --- a/pkg/compute/tasks/disk_resize_task.go +++ b/pkg/compute/tasks/disk_resize_task.go @@ -23,38 +23,43 @@ func init() { func (self *DiskResizeTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { disk := obj.(*models.SDisk) + + guestId, _ := self.Params.GetString("guest_id") + var masterGuest *models.SGuest + if len(guestId) > 0 { + masterGuest = models.GuestManager.FetchGuestById(guestId) + } + storage := disk.GetStorage() host := storage.GetMasterHost() - online := disk.GetRuningGuestCount() > 0 - if online { - for _, guest := range disk.GetGuests() { - host = guest.GetHost() - } + + if masterGuest != nil { + host = masterGuest.GetHost() } + reason := "Cannot find host for disk" if host == nil || host.HostStatus != models.HOST_ONLINE { disk.SetDiskReady(ctx, self.GetUserCred(), reason) self.SetStageFailed(ctx, reason) db.OpsLog.LogEvent(disk, db.ACT_RESIZE_FAIL, reason, self.GetUserCred()) logclient.AddActionLog(disk, logclient.ACT_RESIZE, reason, self.UserCred, false) - } else { - disk.SetStatus(self.GetUserCred(), models.DISK_START_RESIZE, "") + return + } + + disk.SetStatus(self.GetUserCred(), models.DISK_START_RESIZE, "") + if masterGuest == nil { for _, guest := range disk.GetGuests() { guest.SetStatus(self.GetUserCred(), models.VM_RESIZE_DISK, "") } - self.StartResizeDisk(ctx, host, storage, disk, online) } + self.StartResizeDisk(ctx, host, storage, disk, masterGuest) } -func (self *DiskResizeTask) StartResizeDisk(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, online bool) { +func (self *DiskResizeTask) StartResizeDisk(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, guest *models.SGuest) { log.Infof("Resizing disk on host %s ...", host.GetName()) self.SetStage("on_disk_resize_complete", nil) sizeMb, _ := self.GetParams().Int("size") - proc := host.GetHostDriver().RequestResizeDiskOnHost - if online { - proc = host.GetHostDriver().RequestResizeDiskOnHostOnline - } - if err := proc(ctx, host, storage, disk, sizeMb, self); err != nil { + if err := host.GetHostDriver().RequestResizeDiskOnHost(ctx, host, storage, disk, guest, sizeMb, self); err != nil { log.Errorf("request_resize_disk_on_host: %v", err) self.OnStartResizeDiskFailed(ctx, disk, err) return diff --git a/pkg/compute/tasks/eip_allocate_task.go b/pkg/compute/tasks/eip_allocate_task.go index 1f90eaa240..7ec5713f7e 100644 --- a/pkg/compute/tasks/eip_allocate_task.go +++ b/pkg/compute/tasks/eip_allocate_task.go @@ -45,7 +45,7 @@ func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel return } - extEip, err := iregion.CreateEIP(eip.Name, eip.Bandwidth, eip.ChargeType) + extEip, err := iregion.CreateEIP(eip.Name, eip.Bandwidth, eip.ChargeType, eip.BgpType) if err != nil { msg := fmt.Sprintf("create eip fail %s", err) eip.SetStatus(self.UserCred, models.EIP_STATUS_ALLOCATE_FAIL, msg) @@ -66,7 +66,7 @@ func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel if self.Params != nil && self.Params.Contains("instance_id") { self.SetStage("on_eip_associate_complete", nil) - err = eip.StartEipAssociateTask(ctx, self.UserCred, self.Params) + err = eip.StartEipAssociateTask(ctx, self.UserCred, self.Params, "") if err != nil { msg := fmt.Sprintf("start associate task fail %s", err) self.SetStageFailed(ctx, msg) diff --git a/pkg/compute/tasks/eip_dissociate_task.go b/pkg/compute/tasks/eip_dissociate_task.go index 72283c2596..781db4a6f9 100644 --- a/pkg/compute/tasks/eip_dissociate_task.go +++ b/pkg/compute/tasks/eip_dissociate_task.go @@ -68,7 +68,9 @@ func (self *EipDissociateTask) OnInit(ctx context.Context, obj db.IStandaloneMod self.SetStageComplete(ctx, nil) - if eip.AutoDellocate.IsTrue() { + autoDelete := jsonutils.QueryBoolean(self.GetParams(), "auto_delete", false) + + if eip.AutoDellocate.IsTrue() || autoDelete { eip.StartEipDeallocateTask(ctx, self.UserCred, "") } } diff --git a/pkg/compute/tasks/guest_backup_tasks.go b/pkg/compute/tasks/guest_backup_tasks.go index 1e34ddbdf6..2f6a30b7b8 100644 --- a/pkg/compute/tasks/guest_backup_tasks.go +++ b/pkg/compute/tasks/guest_backup_tasks.go @@ -123,6 +123,7 @@ func (self *GuestStartAndSyncToBackupTask) OnStartBackupGuestFailed(ctx context. } func (self *GuestStartAndSyncToBackupTask) OnRequestSyncToBackup(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + guest.SetStatus(self.UserCred, models.VM_BLOCK_STREAM, "OnSyncToBackup") self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/guest_change_config_task.go b/pkg/compute/tasks/guest_change_config_task.go index f8cdcaed36..d8bf510fa7 100644 --- a/pkg/compute/tasks/guest_change_config_task.go +++ b/pkg/compute/tasks/guest_change_config_task.go @@ -81,7 +81,7 @@ func (self *GuestChangeConfigTask) OnDisksResizeComplete(ctx context.Context, ob self.markStageFailed(ctx, guest, fmt.Sprintf("self.GetPendingUsage(&pendingUsage) fail %s", err)) return } - err = disk.StartDiskResizeTask(ctx, self.UserCred, size, self.GetTaskId(), &pendingUsage) + err = disk.StartDiskResizeTask(ctx, self.UserCred, size, self.GetTaskId(), &pendingUsage, guest) if err != nil { self.markStageFailed(ctx, guest, fmt.Sprintf("disk.StartDiskResizeTask fail %s", err)) return @@ -111,109 +111,109 @@ func (self *GuestChangeConfigTask) OnCreateDisksCompleteFailed(ctx context.Conte func (self *GuestChangeConfigTask) OnCreateDisksComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) - var vcpuCount, vmemSize int64 - var paramsError error - var err error - iSku, paramsError := self.Params.GetString("sku_id") - if paramsError == nil { - isku, err := models.ServerSkuManager.FetchById(iSku) - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("Sku_id fail %s", err)) - logclient.AddActionLog(guest, logclient.ACT_VM_CHANGE_FLAVOR, err, self.UserCred, false) - return - } - sku := isku.(*models.SServerSku) - self.Params.Set("instance_type", jsonutils.NewString(sku.GetName())) - vcpuCount = int64(sku.CpuCoreCount) - vmemSize = int64(sku.MemorySizeMB) + if self.Params.Contains("instance_type") || self.Params.Contains("vcpu_count") || self.Params.Contains("vmem_size") { + self.SetStage("OnGuestChangeCpuMemSpecComplete", nil) + instanceType, _ := self.Params.GetString("instance_type") + vcpuCount, _ := self.Params.Int("vcpu_count") + vmemSize, _ := self.Params.Int("vmem_size") + if vcpuCount == 0 { + vcpuCount = int64(guest.VcpuCount) + } + if vmemSize == 0 { + vmemSize = int64(guest.VmemSize) + } + self.startGuestChangeCpuMemSpec(ctx, guest, instanceType, vcpuCount, vmemSize) } else { - iVcpuCount, cpuError := self.Params.Get("vcpu_count") - if iVcpuCount != nil { - vcpuCount, err = iVcpuCount.Int() - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("iVcpuCount.Int() fail %s", err)) - return - } - } - - iVmemSize, memError := self.Params.Get("vmem_size") - if iVmemSize != nil { - vmemSize, err = iVmemSize.Int() - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("iVmemSize.Int fail %s", err)) - return - } - } - - if cpuError == nil || memError == nil { - paramsError = nil - } + self.OnGuestChangeCpuMemSpecComplete(ctx, obj, data) } +} - if paramsError == nil { - err = guest.GetDriver().RequestChangeVmConfig(ctx, guest, self, vcpuCount, vmemSize) - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("guest.GetDriver().RequestChangeVmConfig fail %s", err)) - return - } - var addCpu, addMem = 0, 0 +func (self *GuestChangeConfigTask) startGuestChangeCpuMemSpec(ctx context.Context, guest *models.SGuest, instanceType string, vcpuCount int64, vmemSize int64) { + err := guest.GetDriver().RequestChangeVmConfig(ctx, guest, self, instanceType, vcpuCount, vmemSize) + if err != nil { + self.markStageFailed(ctx, guest, fmt.Sprintf("guest.GetDriver().RequestChangeVmConfig fail %s", err)) + return + } +} + +func (self *GuestChangeConfigTask) OnGuestChangeCpuMemSpecCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + self.markStageFailed(ctx, guest, fmt.Sprintf("guest.GetDriver().RequestChangeVmConfig fail %s", err)) +} + +func (self *GuestChangeConfigTask) OnGuestChangeCpuMemSpecComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + + instanceType, _ := self.Params.GetString("instance_type") + vcpuCount, _ := self.Params.Int("vcpu_count") + vmemSize, _ := self.Params.Int("vmem_size") + + addCpu := int(vcpuCount - int64(guest.VcpuCount)) + addMem := int(vmemSize - int64(guest.VmemSize)) + + _, err := guest.GetModelManager().TableSpec().Update(guest, func() error { if vcpuCount > 0 { - addCpu = int(vcpuCount - int64(guest.VcpuCount)) - if addCpu < 0 { - addCpu = 0 - } + guest.VcpuCount = int8(vcpuCount) } if vmemSize > 0 { - addMem = int(vmemSize - int64(guest.VmemSize)) - if addMem < 0 { - addMem = 0 - } + guest.VmemSize = int(vmemSize) } - _, err = guest.GetModelManager().TableSpec().Update(guest, func() error { - if vcpuCount > 0 { - guest.VcpuCount = int8(vcpuCount) - } - if vmemSize > 0 { - guest.VmemSize = int(vmemSize) - } - return nil - }) - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("Update fail %s", err)) - return - } - var pendingUsage models.SQuota - err = self.GetPendingUsage(&pendingUsage) - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("GetPendingUsage %s", err)) - return - } - // ownerCred := guest.GetOwnerUserCred() - var cancelUsage models.SQuota - if addCpu > 0 { - cancelUsage.Cpu = addCpu - } - if addMem > 0 { - cancelUsage.Memory = addMem - } - - lockman.LockClass(ctx, guest.GetModelManager(), guest.ProjectId) - defer lockman.ReleaseClass(ctx, guest.GetModelManager(), guest.ProjectId) - - err = models.QuotaManager.CancelPendingUsage(ctx, self.UserCred, guest.ProjectId, &pendingUsage, &cancelUsage) - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("CancelPendingUsage fail %s", err)) - return - } - err = self.SetPendingUsage(&pendingUsage) - if err != nil { - self.markStageFailed(ctx, guest, fmt.Sprintf("SetPendingUsage fail %s", err)) - return + if len(instanceType) > 0 { + guest.InstanceType = instanceType } + return nil + }) + if err != nil { + self.markStageFailed(ctx, guest, fmt.Sprintf("Update fail %s", err)) + return } + + var pendingUsage models.SQuota + err = self.GetPendingUsage(&pendingUsage) + if err != nil { + self.markStageFailed(ctx, guest, fmt.Sprintf("GetPendingUsage %s", err)) + return + } + var cancelUsage models.SQuota + if addCpu > 0 { + cancelUsage.Cpu = addCpu + } + if addMem > 0 { + cancelUsage.Memory = addMem + } + + lockman.LockClass(ctx, guest.GetModelManager(), guest.ProjectId) + defer lockman.ReleaseClass(ctx, guest.GetModelManager(), guest.ProjectId) + + err = models.QuotaManager.CancelPendingUsage(ctx, self.UserCred, guest.ProjectId, &pendingUsage, &cancelUsage) + if err != nil { + self.markStageFailed(ctx, guest, fmt.Sprintf("CancelPendingUsage fail %s", err)) + return + } + err = self.SetPendingUsage(&pendingUsage) + if err != nil { + self.markStageFailed(ctx, guest, fmt.Sprintf("SetPendingUsage fail %s", err)) + return + } + + self.OnGuestChangeCpuMemSpecFinish(ctx, guest) +} + +func (self *GuestChangeConfigTask) OnGuestChangeCpuMemSpecFinish(ctx context.Context, guest *models.SGuest) { + self.SetStage("on_sync_config_complete", nil) + err := guest.StartSyncTask(ctx, self.UserCred, false, self.GetTaskId()) + if err != nil { + self.markStageFailed(ctx, guest, fmt.Sprintf("StartSyncstatus fail %s", err)) + return + } +} + +func (self *GuestChangeConfigTask) OnSyncConfigComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + self.SetStage("on_sync_status_complete", nil) - err = guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) + err := guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) if err != nil { self.markStageFailed(ctx, guest, fmt.Sprintf("StartSyncstatus fail %s", err)) return diff --git a/pkg/compute/tasks/guest_create_task.go b/pkg/compute/tasks/guest_create_task.go index 30597090a2..756e25882d 100644 --- a/pkg/compute/tasks/guest_create_task.go +++ b/pkg/compute/tasks/guest_create_task.go @@ -12,6 +12,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -100,8 +101,8 @@ func (self *GuestCreateTask) OnDeployGuestDescComplete(ctx context.Context, obj } func (self *GuestCreateTask) notifyServerCreated(ctx context.Context, guest *models.SGuest) { - guest.NotifyServerEvent(notifyclient.SERVER_CREATED, notifyclient.PRIORITY_IMPORTANT, true) - guest.NotifyAdminServerEvent(ctx, notifyclient.SERVER_CREATED_ADMIN, notifyclient.PRIORITY_IMPORTANT) + guest.NotifyServerEvent(notifyclient.SERVER_CREATED, notify.NotifyPriorityImportant, true) + guest.NotifyAdminServerEvent(ctx, notifyclient.SERVER_CREATED_ADMIN, notify.NotifyPriorityImportant) } func (self *GuestCreateTask) OnDeployGuestDescCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { @@ -116,9 +117,36 @@ func (self *GuestCreateTask) OnDeployGuestDescCompleteFailed(ctx context.Context func (self *GuestCreateTask) OnAutoStartGuest(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) self.SetStageComplete(ctx, guest.GetShortDesc(ctx)) + self.StartEipSubTask(ctx, guest) } func (self *GuestCreateTask) OnSyncStatusComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) self.SetStageComplete(ctx, guest.GetShortDesc(ctx)) + self.StartEipSubTask(ctx, guest) +} + +func (self *GuestCreateTask) StartEipSubTask(ctx context.Context, guest *models.SGuest) { + eipId, _ := self.Params.GetString("eip_id") + if len(eipId) > 0 { + eipObj, err := models.ElasticipManager.FetchById(eipId) + if err != nil { + log.Errorf("fail to get eip %s %s", eipId, err) + return + } + eip := eipObj.(*models.SElasticip) + eip.StartEipAssociateInstanceTask(ctx, self.UserCred, guest, "") + return + } + eipBw, _ := self.Params.Int("eip_bw") + if eipBw > 0 { + pendingUsage := models.SQuota{} + err := self.GetPendingUsage(&pendingUsage) + if err != nil { + log.Errorf("GetPendingUsage fail %s", err) + } + eipChargeType, _ := self.Params.GetString("eip_charge_type") + models.ElasticipManager.AllocateEipAndAssociateVM(ctx, self.UserCred, guest, int(eipBw), eipChargeType, &pendingUsage) + self.SetPendingUsage(&pendingUsage) + } } diff --git a/pkg/compute/tasks/guest_delete_backup_task.go b/pkg/compute/tasks/guest_delete_backup_task.go new file mode 100644 index 0000000000..dde3ed2d09 --- /dev/null +++ b/pkg/compute/tasks/guest_delete_backup_task.go @@ -0,0 +1,87 @@ +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" +) + +func init() { + taskman.RegisterTask(GuestDeleteOnHostTask{}) +} + +type GuestDeleteOnHostTask struct { + SGuestBaseTask +} + +func (self *GuestDeleteOnHostTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + hostId, err := self.Params.GetString("host_id") + if err != nil { + self.SetStageFailed(ctx, "Missing param host id") + return + } + host := models.HostManager.FetchHostById(hostId) + if host == nil { + self.SetStageFailed(ctx, "Host is nil") + return + } + guest := obj.(*models.SGuest) + + self.SetStage("OnStopGuest", nil) + self.Params.Set("is_force", jsonutils.JSONTrue) + if err := guest.GetDriver().RequestStopOnHost(ctx, guest, host, self); err != nil { + self.SetStageFailed(ctx, err.Error()) + return + } +} + +func (self *GuestDeleteOnHostTask) OnStopGuest(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + hostId, _ := self.Params.GetString("host_id") + host := models.HostManager.FetchHostById(hostId) + + isPurge := jsonutils.QueryBoolean(self.Params, "purge", false) + disks := guest.GetDisks() + + for _, guestDiks := range disks { + disk := guestDiks.GetDisk() + storage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + if storage != nil && !isPurge { + if err := host.GetHostDriver().RequestDeallocateBackupDiskOnHost(ctx, host, storage, disk, self); err != nil { + self.SetStageFailed(ctx, err.Error()) + return + } + } + _, err := models.DiskManager.TableSpec().Update(disk, func() error { + disk.BackupStorageId = "" + return nil + }) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + return + } + } + if !isPurge { + self.SetStage("OnUnDeployGuest", nil) + guest.StartUndeployGuestTask(ctx, self.GetUserCred(), self.GetTaskId(), hostId) + } else { + self.OnUnDeployGuest(ctx, guest, nil) + } +} + +func (self *GuestDeleteOnHostTask) OnUnDeployGuest(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + hostId, _ := self.Params.GetString("host_id") + if guest.BackupHostId == hostId { + _, err := models.GuestManager.TableSpec().Update(guest, func() error { + guest.BackupHostId = "" + return nil + }) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + return + } + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/guest_delete_task.go b/pkg/compute/tasks/guest_delete_task.go index 5521851d8d..d32677445f 100644 --- a/pkg/compute/tasks/guest_delete_task.go +++ b/pkg/compute/tasks/guest_delete_task.go @@ -13,6 +13,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -73,7 +74,7 @@ func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, obj db.ISt self.OnEipDissociateComplete(ctx, guest, nil) } else { self.SetStage("on_eip_dissociate_complete", nil) - eip.StartEipDissociateTask(ctx, self.UserCred, self.GetTaskId()) + eip.StartEipDissociateTask(ctx, self.UserCred, false, self.GetTaskId()) } } else { self.OnEipDissociateComplete(ctx, obj, nil) @@ -252,11 +253,12 @@ func (self *GuestDeleteTask) OnGuestDeleteComplete(ctx context.Context, obj db.I } func (self *GuestDeleteTask) DeleteGuest(ctx context.Context, guest *models.SGuest) { + isPendingDeleted := guest.PendingDeleted guest.RealDelete(ctx, self.UserCred) guest.RemoveAllMetadata(ctx, self.UserCred) db.OpsLog.LogEvent(guest, db.ACT_DELOCATE, nil, self.UserCred) logclient.AddActionLog(guest, logclient.ACT_DELETE, nil, self.UserCred, true) - if !guest.IsSystem && !guest.PendingDeleted { + if !guest.IsSystem && !isPendingDeleted { self.NotifyServerDeleted(ctx, guest) } models.HostManager.ClearSchedDescCache(guest.HostId) @@ -264,6 +266,6 @@ func (self *GuestDeleteTask) DeleteGuest(ctx context.Context, guest *models.SGue } func (self *GuestDeleteTask) NotifyServerDeleted(ctx context.Context, guest *models.SGuest) { - guest.NotifyServerEvent(notifyclient.SERVER_DELETED, notifyclient.PRIORITY_IMPORTANT, false) - guest.NotifyAdminServerEvent(ctx, notifyclient.SERVER_DELETED_ADMIN, notifyclient.PRIORITY_IMPORTANT) + guest.NotifyServerEvent(notifyclient.SERVER_DELETED, notify.NotifyPriorityImportant, false) + guest.NotifyAdminServerEvent(ctx, notifyclient.SERVER_DELETED_ADMIN, notify.NotifyPriorityImportant) } diff --git a/pkg/compute/tasks/guest_rebuild_root_task.go b/pkg/compute/tasks/guest_rebuild_root_task.go index 78c2780660..ab5f47ad53 100644 --- a/pkg/compute/tasks/guest_rebuild_root_task.go +++ b/pkg/compute/tasks/guest_rebuild_root_task.go @@ -11,6 +11,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -148,7 +149,7 @@ func (self *GuestRebuildRootTask) OnRebuildAllDisksComplete(ctx context.Context, } } db.OpsLog.LogEvent(guest, db.ACT_REBUILD_ROOT, "", self.UserCred) - guest.NotifyServerEvent(notifyclient.SERVER_REBUILD_ROOT, notifyclient.PRIORITY_IMPORTANT, true) + guest.NotifyServerEvent(notifyclient.SERVER_REBUILD_ROOT, notify.NotifyPriorityImportant, true) self.SetStage("OnSyncStatusComplete", nil) guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) } diff --git a/pkg/compute/tasks/guest_undeploy_task.go b/pkg/compute/tasks/guest_undeploy_task.go index f10cd631a4..fc34aa6f52 100644 --- a/pkg/compute/tasks/guest_undeploy_task.go +++ b/pkg/compute/tasks/guest_undeploy_task.go @@ -25,7 +25,7 @@ func (self *GuestUndeployTask) OnInit(ctx context.Context, obj db.IStandaloneMod self.SetStage("OnGuestUndeployComplete", nil) if len(targetHostId) == 0 { if len(guest.BackupHostId) > 0 { - self.SetStage("OnMasetHostUndeployGuestComplete", nil) + self.SetStage("OnMasterHostUndeployGuestComplete", nil) } targetHostId = guest.HostId } @@ -43,7 +43,7 @@ func (self *GuestUndeployTask) OnInit(ctx context.Context, obj db.IStandaloneMod } } -func (self *GuestUndeployTask) OnMasetHostUndeployGuestComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { +func (self *GuestUndeployTask) OnMasterHostUndeployGuestComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { self.SetStage("OnGuestUndeployComplete", nil) host := models.HostManager.FetchHostById(guest.BackupHostId) if host != nil { diff --git a/pkg/compute/tasks/loadbalancer_acl_create_task.go b/pkg/compute/tasks/loadbalancer_acl_create_task.go new file mode 100644 index 0000000000..53deb86a8b --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_acl_create_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerAclCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerAclCreateTask{}) +} + +func (self *LoadbalancerAclCreateTask) taskFail(ctx context.Context, lbacl *models.SLoadbalancerAcl, reason string) { + lbacl.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lbacl, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbacl.Id, lbacl.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerAclCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbacl := obj.(*models.SLoadbalancerAcl) + region := lbacl.GetRegion() + if region == nil { + self.taskFail(ctx, lbacl, fmt.Sprintf("failed to find region for lbacl %s", lbacl.Name)) + return + } + self.SetStage("OnLoadbalancerAclCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerAcl(ctx, self.GetUserCred(), lbacl, self); err != nil { + self.taskFail(ctx, lbacl, err.Error()) + } +} + +func (self *LoadbalancerAclCreateTask) OnLoadbalancerAclCreateComplete(ctx context.Context, lbacl *models.SLoadbalancerAcl, data jsonutils.JSONObject) { + lbacl.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lbacl, db.ACT_ALLOCATE, lbacl.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerAclCreateTask) OnLoadbalancerAclCreateCompleteFailed(ctx context.Context, lbacl *models.SLoadbalancerAcl, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbacl, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_acl_delete_task.go b/pkg/compute/tasks/loadbalancer_acl_delete_task.go new file mode 100644 index 0000000000..ab95d23e0b --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_acl_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerAclDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerAclDeleteTask{}) +} + +func (self *LoadbalancerAclDeleteTask) taskFail(ctx context.Context, lbacl *models.SLoadbalancerAcl, reason string) { + lbacl.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + db.OpsLog.LogEvent(lbacl, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_DELETE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbacl.Id, lbacl.Name, models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerAclDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbacl := obj.(*models.SLoadbalancerAcl) + region := lbacl.GetRegion() + if region == nil { + self.taskFail(ctx, lbacl, fmt.Sprintf("failed to find region for lbacl %s", lbacl.Name)) + return + } + self.SetStage("OnLoadbalancerAclDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancerAcl(ctx, self.GetUserCred(), lbacl, self); err != nil { + self.taskFail(ctx, lbacl, err.Error()) + } +} + +func (self *LoadbalancerAclDeleteTask) OnLoadbalancerAclDeleteComplete(ctx context.Context, lbacl *models.SLoadbalancerAcl, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lbacl, db.ACT_DELETE, lbacl.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_DELETE, nil, self.UserCred, true) + lbacl.DoPendingDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerAclDeleteTask) OnLoadbalancerAclDeleteCompleteFailed(ctx context.Context, lbacl *models.SLoadbalancerAcl, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbacl, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_acl_sync_task.go b/pkg/compute/tasks/loadbalancer_acl_sync_task.go new file mode 100644 index 0000000000..50277c984c --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_acl_sync_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerAclSyncTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerAclSyncTask{}) +} + +func (self *LoadbalancerAclSyncTask) taskFail(ctx context.Context, lbacl *models.SLoadbalancerAcl, reason string) { + lbacl.SetStatus(self.GetUserCred(), models.LB_SYNC_CONF_FAILED, reason) + db.OpsLog.LogEvent(lbacl, db.ACT_SYNC_CONF, reason, self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_SYNC_CONF, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbacl.Id, lbacl.Name, models.LB_SYNC_CONF_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerAclSyncTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbacl := obj.(*models.SLoadbalancerAcl) + region := lbacl.GetRegion() + if region == nil { + self.taskFail(ctx, lbacl, fmt.Sprintf("failed to find region for lbacl %s", lbacl.Name)) + return + } + self.SetStage("OnLoadbalancerAclSyncComplete", nil) + if err := region.GetDriver().RequestSyncLoadbalancerAcl(ctx, self.GetUserCred(), lbacl, self); err != nil { + self.taskFail(ctx, lbacl, err.Error()) + } +} + +func (self *LoadbalancerAclSyncTask) OnLoadbalancerAclSyncComplete(ctx context.Context, lbacl *models.SLoadbalancerAcl, data jsonutils.JSONObject) { + lbacl.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lbacl, db.ACT_SYNC_CONF, lbacl.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_SYNC_CONF, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerAclSyncTask) OnLoadbalancerAclSyncCompleteFailed(ctx context.Context, lbacl *models.SLoadbalancerAcl, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbacl, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_backend_create_task.go b/pkg/compute/tasks/loadbalancer_backend_create_task.go new file mode 100644 index 0000000000..93325910ee --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_backend_create_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerBackendCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerBackendCreateTask{}) +} + +func (self *LoadbalancerBackendCreateTask) taskFail(ctx context.Context, lbb *models.SLoadbalancerBackend, reason string) { + lbb.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lbb, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbb, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbb.Id, lbb.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerBackendCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbb := obj.(*models.SLoadbalancerBackend) + region := lbb.GetRegion() + if region == nil { + self.taskFail(ctx, lbb, fmt.Sprintf("failed to find region for lbb %s", lbb.Name)) + return + } + self.SetStage("OnLoadbalancerBackendCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerBackend(ctx, self.GetUserCred(), lbb, self); err != nil { + self.taskFail(ctx, lbb, err.Error()) + } +} + +func (self *LoadbalancerBackendCreateTask) OnLoadbalancerBackendCreateComplete(ctx context.Context, lbb *models.SLoadbalancerBackend, data jsonutils.JSONObject) { + lbb.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lbb, db.ACT_ALLOCATE, lbb.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbb, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerBackendCreateTask) OnLoadbalancerBackendCreateCompleteFailed(ctx context.Context, lbb *models.SLoadbalancerBackend, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbb, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_backend_delete_task.go b/pkg/compute/tasks/loadbalancer_backend_delete_task.go new file mode 100644 index 0000000000..7aba65751a --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_backend_delete_task.go @@ -0,0 +1,47 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type LoadbalancerBackendDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerBackendDeleteTask{}) +} + +func (self *LoadbalancerBackendDeleteTask) taskFail(ctx context.Context, lbb *models.SLoadbalancerBackend, reason string) { + lbb.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerBackendDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbb := obj.(*models.SLoadbalancerBackend) + region := lbb.GetRegion() + if region == nil { + self.taskFail(ctx, lbb, fmt.Sprintf("failed to find region for lbb %s", lbb.Name)) + return + } + self.SetStage("OnLoadbalancerBackendDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancerBackend(ctx, self.GetUserCred(), lbb, self); err != nil { + self.taskFail(ctx, lbb, err.Error()) + } +} + +func (self *LoadbalancerBackendDeleteTask) OnLoadbalancerBackendDeleteComplete(ctx context.Context, lbb *models.SLoadbalancerBackend, data jsonutils.JSONObject) { + lbb.DoPendingDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerBackendDeleteTask) OnLoadbalancerBackendDeleteCompleteFailed(ctx context.Context, lbb *models.SLoadbalancerBackend, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbb, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go b/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go new file mode 100644 index 0000000000..1c7965c581 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go @@ -0,0 +1,57 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerLoadbalancerBackendGroupCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerLoadbalancerBackendGroupCreateTask{}) +} + +func (self *LoadbalancerLoadbalancerBackendGroupCreateTask) taskFail(ctx context.Context, lbacl *models.SLoadbalancerBackendGroup, reason string) { + lbacl.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lbacl, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbacl, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbacl.Id, lbacl.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerLoadbalancerBackendGroupCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbbg := obj.(*models.SLoadbalancerBackendGroup) + region := lbbg.GetRegion() + if region == nil { + self.taskFail(ctx, lbbg, fmt.Sprintf("failed to find region for lb backendgroup %s", lbbg.Name)) + return + } + backends := []cloudprovider.SLoadbalancerBackend{} + self.GetParams().Unmarshal(&backends, "backends") + self.SetStage("OnLoadbalancerBackendGroupCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerBackendGroup(ctx, self.GetUserCred(), lbbg, backends, self); err != nil { + self.taskFail(ctx, lbbg, err.Error()) + } +} + +func (self *LoadbalancerLoadbalancerBackendGroupCreateTask) OnLoadbalancerBackendGroupCreateComplete(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup, data jsonutils.JSONObject) { + lbbg.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lbbg, db.ACT_ALLOCATE, lbbg.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbbg, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerLoadbalancerBackendGroupCreateTask) OnLoadbalancerBackendGroupCreateCompleteFailed(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbbg, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_backendgroup_delete_task.go b/pkg/compute/tasks/loadbalancer_backendgroup_delete_task.go new file mode 100644 index 0000000000..7ecf01d9dd --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_backendgroup_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerBackendGroupDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerBackendGroupDeleteTask{}) +} + +func (self *LoadbalancerBackendGroupDeleteTask) taskFail(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup, reason string) { + lbbg.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + db.OpsLog.LogEvent(lbbg, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbbg, logclient.ACT_DELETE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbbg.Id, lbbg.Name, models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerBackendGroupDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbbg := obj.(*models.SLoadbalancerBackendGroup) + region := lbbg.GetRegion() + if region == nil { + self.taskFail(ctx, lbbg, fmt.Sprintf("failed to find region for lb %s", lbbg.Name)) + return + } + self.SetStage("OnLoadbalancerBackendGroupDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancerBackendGroup(ctx, self.GetUserCred(), lbbg, self); err != nil { + self.taskFail(ctx, lbbg, err.Error()) + } +} + +func (self *LoadbalancerBackendGroupDeleteTask) OnLoadbalancerBackendGroupDeleteComplete(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lbbg, db.ACT_DELETE, lbbg.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbbg, logclient.ACT_DELETE, nil, self.UserCred, true) + lbbg.PreDeleteSubs(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerBackendGroupDeleteTask) OnLoadbalancerBackendGroupDeleteCompleteFailed(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbbg, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_certificate_create_task.go b/pkg/compute/tasks/loadbalancer_certificate_create_task.go new file mode 100644 index 0000000000..e696db3b46 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_certificate_create_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerCertificateCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerCertificateCreateTask{}) +} + +func (self *LoadbalancerCertificateCreateTask) taskFail(ctx context.Context, lbcert *models.SLoadbalancerCertificate, reason string) { + lbcert.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lbcert, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbcert, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbcert.Id, lbcert.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerCertificateCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbcert := obj.(*models.SLoadbalancerCertificate) + region := lbcert.GetRegion() + if region == nil { + self.taskFail(ctx, lbcert, fmt.Sprintf("failed to find region for lbcert %s", lbcert.Name)) + return + } + self.SetStage("OnLoadbalancerCertificateCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerCertificate(ctx, self.GetUserCred(), lbcert, self); err != nil { + self.taskFail(ctx, lbcert, err.Error()) + } +} + +func (self *LoadbalancerCertificateCreateTask) OnLoadbalancerCertificateCreateComplete(ctx context.Context, lbcert *models.SLoadbalancerCertificate, data jsonutils.JSONObject) { + lbcert.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lbcert, db.ACT_ALLOCATE, lbcert.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbcert, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerCertificateCreateTask) OnLoadbalancerCertificateCreateCompleteFailed(ctx context.Context, lbcert *models.SLoadbalancerCertificate, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbcert, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_certificate_delete_task.go b/pkg/compute/tasks/loadbalancer_certificate_delete_task.go new file mode 100644 index 0000000000..1480f483ec --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_certificate_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerCertificateDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerCertificateDeleteTask{}) +} + +func (self *LoadbalancerCertificateDeleteTask) taskFail(ctx context.Context, lbcert *models.SLoadbalancerCertificate, reason string) { + lbcert.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + db.OpsLog.LogEvent(lbcert, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbcert, logclient.ACT_DELETE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbcert.Id, lbcert.Name, models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerCertificateDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbcert := obj.(*models.SLoadbalancerCertificate) + region := lbcert.GetRegion() + if region == nil { + self.taskFail(ctx, lbcert, fmt.Sprintf("failed to find region for lbcert %s", lbcert.Name)) + return + } + self.SetStage("OnLoadbalancerCertificateDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancerCertificate(ctx, self.GetUserCred(), lbcert, self); err != nil { + self.taskFail(ctx, lbcert, err.Error()) + } +} + +func (self *LoadbalancerCertificateDeleteTask) OnLoadbalancerCertificateDeleteComplete(ctx context.Context, lbcert *models.SLoadbalancerCertificate, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lbcert, db.ACT_DELETE, lbcert.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbcert, logclient.ACT_DELETE, nil, self.UserCred, true) + lbcert.DoPendingDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerCertificateDeleteTask) OnLoadbalancerCertificateDeleteCompleteFailed(ctx context.Context, lbcert *models.SLoadbalancerCertificate, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbcert, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_create_task.go b/pkg/compute/tasks/loadbalancer_create_task.go new file mode 100644 index 0000000000..4e16013038 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_create_task.go @@ -0,0 +1,65 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerCreateTask{}) +} + +func (self *LoadbalancerCreateTask) taskFail(ctx context.Context, lb *models.SLoadbalancer, reason string) { + lb.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lb, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lb.Id, lb.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lb := obj.(*models.SLoadbalancer) + region := lb.GetRegion() + if region == nil { + self.taskFail(ctx, lb, fmt.Sprintf("failed to find region for lb %s", lb.Name)) + return + } + self.SetStage("OnLoadbalancerCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancer(ctx, self.GetUserCred(), lb, self); err != nil { + self.taskFail(ctx, lb, err.Error()) + } +} + +func (self *LoadbalancerCreateTask) OnLoadbalancerCreateComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lb, db.ACT_ALLOCATE, lb.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStage("OnLoadbalancerStartComplete", nil) + lb.StartLoadBalancerStartTask(ctx, self.GetUserCred(), self.GetTaskId()) +} + +func (self *LoadbalancerCreateTask) OnLoadbalancerCreateCompleteFailed(ctx context.Context, lb *models.SLoadbalancer, reason jsonutils.JSONObject) { + self.taskFail(ctx, lb, reason.String()) +} + +func (self *LoadbalancerCreateTask) OnLoadbalancerStartComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerCreateTask) OnLoadbalancerStartCompleteFailed(ctx context.Context, lb *models.SLoadbalancer, reason jsonutils.JSONObject) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_DISABLED, reason.String()) + self.SetStageFailed(ctx, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_delete_task.go b/pkg/compute/tasks/loadbalancer_delete_task.go new file mode 100644 index 0000000000..bb4fef41d4 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerDeleteTask{}) +} + +func (self *LoadbalancerDeleteTask) taskFail(ctx context.Context, lb *models.SLoadbalancer, reason string) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + db.OpsLog.LogEvent(lb, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_DELETE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lb.Id, lb.Name, models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lb := obj.(*models.SLoadbalancer) + region := lb.GetRegion() + if region == nil { + self.taskFail(ctx, lb, fmt.Sprintf("failed to find region for lb %s", lb.Name)) + return + } + self.SetStage("OnLoadbalancerDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancer(ctx, self.GetUserCred(), lb, self); err != nil { + self.taskFail(ctx, lb, err.Error()) + } +} + +func (self *LoadbalancerDeleteTask) OnLoadbalancerDeleteComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lb, db.ACT_DELETE, lb.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_DELETE, nil, self.UserCred, true) + lb.PendingDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerDeleteTask) OnLoadbalancerDeleteCompleteFailed(ctx context.Context, lb *models.SLoadbalancer, reason jsonutils.JSONObject) { + self.taskFail(ctx, lb, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_create_task.go b/pkg/compute/tasks/loadbalancer_listener_create_task.go new file mode 100644 index 0000000000..76b91f7363 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_create_task.go @@ -0,0 +1,65 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerCreateTask{}) +} + +func (self *LoadbalancerListenerCreateTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason string) { + lblis.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lblis, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lblis.Id, lblis.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lblis := obj.(*models.SLoadbalancerListener) + region := lblis.GetRegion() + if region == nil { + self.taskFail(ctx, lblis, fmt.Sprintf("failed to find region for lblis %s", lblis.Name)) + return + } + self.SetStage("OnLoadbalancerListenerCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerListener(ctx, self.GetUserCred(), lblis, self); err != nil { + self.taskFail(ctx, lblis, err.Error()) + } +} + +func (self *LoadbalancerListenerCreateTask) OnLoadbalancerListenerCreateComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lblis, db.ACT_ALLOCATE, lblis.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStage("OnLoadbalancerListenerStartComplete", nil) + lblis.StartLoadBalancerListenerStartTask(ctx, self.GetUserCred(), self.GetTaskId()) +} + +func (self *LoadbalancerListenerCreateTask) OnLoadbalancerListenerCreateCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + self.taskFail(ctx, lblis, reason.String()) +} + +func (self *LoadbalancerListenerCreateTask) OnLoadbalancerListenerStartComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerCreateTask) OnLoadbalancerListenerStartCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_DISABLED, reason.String()) + self.SetStageFailed(ctx, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_delete_task.go b/pkg/compute/tasks/loadbalancer_listener_delete_task.go new file mode 100644 index 0000000000..23c5067eb9 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerDeleteTask{}) +} + +func (self *LoadbalancerListenerDeleteTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason string) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + db.OpsLog.LogEvent(lblis, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_DELETE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lblis.Id, lblis.Name, models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lblis := obj.(*models.SLoadbalancerListener) + region := lblis.GetRegion() + if region == nil { + self.taskFail(ctx, lblis, fmt.Sprintf("failed to find region for lblis %s", lblis.Name)) + return + } + self.SetStage("OnLoadbalancerListenerDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancerListener(ctx, self.GetUserCred(), lblis, self); err != nil { + self.taskFail(ctx, lblis, err.Error()) + } +} + +func (self *LoadbalancerListenerDeleteTask) OnLoadbalancerListenerDeleteComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lblis, db.ACT_DELETE, lblis.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_DELETE, nil, self.UserCred, true) + lblis.PreDeleteSubs(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerDeleteTask) OnLoadbalancerListenerDeleteCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + self.taskFail(ctx, lblis, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go b/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go new file mode 100644 index 0000000000..d006ee4b1e --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerRuleCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerRuleCreateTask{}) +} + +func (self *LoadbalancerListenerRuleCreateTask) taskFail(ctx context.Context, lbr *models.SLoadbalancerListenerRule, reason string) { + lbr.SetStatus(self.GetUserCred(), models.LB_CREATE_FAILED, reason) + db.OpsLog.LogEvent(lbr, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbr, logclient.ACT_CREATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbr.Id, lbr.Name, models.LB_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerRuleCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbr := obj.(*models.SLoadbalancerListenerRule) + region := lbr.GetRegion() + if region == nil { + self.taskFail(ctx, lbr, fmt.Sprintf("failed to find region for lbr %s", lbr.Name)) + return + } + self.SetStage("OnLoadbalancerListenerRuleCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerListenerRule(ctx, self.GetUserCred(), lbr, self); err != nil { + self.taskFail(ctx, lbr, err.Error()) + } +} + +func (self *LoadbalancerListenerRuleCreateTask) OnLoadbalancerListenerRuleCreateComplete(ctx context.Context, lbr *models.SLoadbalancerListenerRule, data jsonutils.JSONObject) { + lbr.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lbr, db.ACT_ALLOCATE, lbr.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbr, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerRuleCreateTask) OnLoadbalancerListenerRuleCreateCompleteFailed(ctx context.Context, lbr *models.SLoadbalancerListenerRule, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbr, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_rule_delete_task.go b/pkg/compute/tasks/loadbalancer_listener_rule_delete_task.go new file mode 100644 index 0000000000..5774ae1a2d --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_rule_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerRuleDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerRuleDeleteTask{}) +} + +func (self *LoadbalancerListenerRuleDeleteTask) taskFail(ctx context.Context, lbr *models.SLoadbalancerListenerRule, reason string) { + lbr.SetStatus(self.GetUserCred(), models.LB_STATUS_DELETE_FAILED, reason) + db.OpsLog.LogEvent(lbr, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLog(lbr, logclient.ACT_DELETE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lbr.Id, lbr.Name, models.LB_STATUS_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerRuleDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbr := obj.(*models.SLoadbalancerListenerRule) + region := lbr.GetRegion() + if region == nil { + self.taskFail(ctx, lbr, fmt.Sprintf("failed to find region for lbr %s", lbr.Name)) + return + } + self.SetStage("OnLoadbalancerListenerRuleDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteLoadbalancerListenerRule(ctx, self.GetUserCred(), lbr, self); err != nil { + self.taskFail(ctx, lbr, err.Error()) + } +} + +func (self *LoadbalancerListenerRuleDeleteTask) OnLoadbalancerListenerRuleDeleteComplete(ctx context.Context, lbr *models.SLoadbalancerListenerRule, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lbr, db.ACT_DELETE, lbr.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lbr, logclient.ACT_DELETE, nil, self.UserCred, true) + lbr.DoPendingDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerRuleDeleteTask) OnLoadbalancerListenerRuleDeleteCompleteFailed(ctx context.Context, lbr *models.SLoadbalancerListenerRule, reason jsonutils.JSONObject) { + self.taskFail(ctx, lbr, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_start_task.go b/pkg/compute/tasks/loadbalancer_listener_start_task.go new file mode 100644 index 0000000000..8cc81b96e8 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_start_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerStartTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerStartTask{}) +} + +func (self *LoadbalancerListenerStartTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason string) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_DISABLED, reason) + db.OpsLog.LogEvent(lblis, db.ACT_ENABLE, reason, self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_ENABLE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lblis.Id, lblis.Name, models.LB_STATUS_DISABLED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerStartTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lblis := obj.(*models.SLoadbalancerListener) + region := lblis.GetRegion() + if region == nil { + self.taskFail(ctx, lblis, fmt.Sprintf("failed to find region for lblis %s", lblis.Name)) + return + } + self.SetStage("OnLoadbalancerListenerStartComplete", nil) + if err := region.GetDriver().RequestStartLoadbalancerListener(ctx, self.GetUserCred(), lblis, self); err != nil { + self.taskFail(ctx, lblis, err.Error()) + } +} + +func (self *LoadbalancerListenerStartTask) OnLoadbalancerListenerStartComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lblis, db.ACT_ENABLE, lblis.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_ENABLE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerStartTask) OnLoadbalancerListenerStartCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + self.taskFail(ctx, lblis, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_stop_task.go b/pkg/compute/tasks/loadbalancer_listener_stop_task.go new file mode 100644 index 0000000000..323606f48d --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_stop_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerStopTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerStopTask{}) +} + +func (self *LoadbalancerListenerStopTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason string) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, reason) + db.OpsLog.LogEvent(lblis, db.ACT_DISABLE, reason, self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_DISABLE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lblis.Id, lblis.Name, models.LB_STATUS_ENABLED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerStopTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lblis := obj.(*models.SLoadbalancerListener) + region := lblis.GetRegion() + if region == nil { + self.taskFail(ctx, lblis, fmt.Sprintf("failed to find region for lblis %s", lblis.Name)) + return + } + self.SetStage("OnLoadbalancerListenerStopComplete", nil) + if err := region.GetDriver().RequestStopLoadbalancerListener(ctx, self.GetUserCred(), lblis, self); err != nil { + self.taskFail(ctx, lblis, err.Error()) + } +} + +func (self *LoadbalancerListenerStopTask) OnLoadbalancerListenerStopComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_DISABLED, "") + db.OpsLog.LogEvent(lblis, db.ACT_DISABLE, lblis.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_DISABLE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerStopTask) OnLoadbalancerListenerStopCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + self.taskFail(ctx, lblis, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_sync_task.go b/pkg/compute/tasks/loadbalancer_listener_sync_task.go new file mode 100644 index 0000000000..6b00691e5c --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_sync_task.go @@ -0,0 +1,63 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerSyncTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerSyncTask{}) +} + +func (self *LoadbalancerListenerSyncTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason string) { + lblis.SetStatus(self.GetUserCred(), models.LB_SYNC_CONF_FAILED, reason) + db.OpsLog.LogEvent(lblis, db.ACT_SYNC_CONF, reason, self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_SYNC_CONF, reason, self.UserCred, false) + notifyclient.NotifySystemError(lblis.Id, lblis.Name, models.LB_SYNC_CONF_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerSyncTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lblis := obj.(*models.SLoadbalancerListener) + region := lblis.GetRegion() + if region == nil { + self.taskFail(ctx, lblis, fmt.Sprintf("failed to find region for lblis %s", lblis.Name)) + return + } + self.SetStage("OnLoadbalancerListenerSyncComplete", nil) + if err := region.GetDriver().RequestSyncLoadbalancerListener(ctx, self.GetUserCred(), lblis, self); err != nil { + self.taskFail(ctx, lblis, err.Error()) + } +} + +func (self *LoadbalancerListenerSyncTask) OnLoadbalancerListenerSyncComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lblis, db.ACT_SYNC_CONF, lblis.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_SYNC_CONF, nil, self.UserCred, true) + self.SetStage("OnLoadbalancerListenerSyncStatusComplete", nil) + lblis.StartLoadBalancerListenerSyncstatusTask(ctx, self.GetUserCred(), self.GetParams(), self.GetTaskId()) +} + +func (self *LoadbalancerListenerSyncTask) OnLoadbalancerListenerSyncCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + self.taskFail(ctx, lblis, reason.String()) +} + +func (self *LoadbalancerListenerSyncTask) OnLoadbalancerListenerSyncStatusComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerSyncTask) OnLoadbalancerListenerSyncStatusCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_UNKNOWN, reason.String()) + self.SetStageFailed(ctx, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_listener_syncstatus_task.go b/pkg/compute/tasks/loadbalancer_listener_syncstatus_task.go new file mode 100644 index 0000000000..fc3a64cefd --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_listener_syncstatus_task.go @@ -0,0 +1,53 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerListenerSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerListenerSyncstatusTask{}) +} + +func (self *LoadbalancerListenerSyncstatusTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason string) { + lblis.SetStatus(self.GetUserCred(), models.LB_STATUS_UNKNOWN, reason) + db.OpsLog.LogEvent(lblis, db.ACT_SYNC_STATUS, reason, self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_SYNC_STATUS, reason, self.UserCred, false) + notifyclient.NotifySystemError(lblis.Id, lblis.Name, models.LB_SYNC_CONF_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerListenerSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lblis := obj.(*models.SLoadbalancerListener) + region := lblis.GetRegion() + if region == nil { + self.taskFail(ctx, lblis, fmt.Sprintf("failed to find region for lblis %s", lblis.Name)) + return + } + self.SetStage("OnLoadbalancerListenerSyncstatusComplete", nil) + if err := region.GetDriver().RequestSyncstatusLoadbalancerListener(ctx, self.GetUserCred(), lblis, self); err != nil { + self.taskFail(ctx, lblis, err.Error()) + } +} + +func (self *LoadbalancerListenerSyncstatusTask) OnLoadbalancerListenerSyncstatusComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lblis, db.ACT_SYNC_STATUS, lblis.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lblis, logclient.ACT_SYNC_STATUS, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerListenerSyncstatusTask) OnLoadbalancerListenerSyncstatusCompleteFailed(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { + self.taskFail(ctx, lblis, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_start_task.go b/pkg/compute/tasks/loadbalancer_start_task.go new file mode 100644 index 0000000000..b402cf8109 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_start_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerStartTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerStartTask{}) +} + +func (self *LoadbalancerStartTask) taskFail(ctx context.Context, lb *models.SLoadbalancer, reason string) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_DISABLED, reason) + db.OpsLog.LogEvent(lb, db.ACT_ENABLE, reason, self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_ENABLE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lb.Id, lb.Name, models.LB_STATUS_DISABLED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerStartTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lb := obj.(*models.SLoadbalancer) + region := lb.GetRegion() + if region == nil { + self.taskFail(ctx, lb, fmt.Sprintf("failed to find region for lb %s", lb.Name)) + return + } + self.SetStage("OnLoadbalancerStartComplete", nil) + if err := region.GetDriver().RequestStartLoadbalancer(ctx, self.GetUserCred(), lb, self); err != nil { + self.taskFail(ctx, lb, err.Error()) + } +} + +func (self *LoadbalancerStartTask) OnLoadbalancerStartComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, "") + db.OpsLog.LogEvent(lb, db.ACT_ENABLE, lb.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_ENABLE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerStartTask) OnLoadbalancerStartCompleteFailed(ctx context.Context, lb *models.SLoadbalancer, reason jsonutils.JSONObject) { + self.taskFail(ctx, lb, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_stop_task.go b/pkg/compute/tasks/loadbalancer_stop_task.go new file mode 100644 index 0000000000..4457466f99 --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_stop_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerStopTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerStopTask{}) +} + +func (self *LoadbalancerStopTask) taskFail(ctx context.Context, lb *models.SLoadbalancer, reason string) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_ENABLED, reason) + db.OpsLog.LogEvent(lb, db.ACT_DISABLE, reason, self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_DISABLE, reason, self.UserCred, false) + notifyclient.NotifySystemError(lb.Id, lb.Name, models.LB_STATUS_ENABLED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerStopTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lb := obj.(*models.SLoadbalancer) + region := lb.GetRegion() + if region == nil { + self.taskFail(ctx, lb, fmt.Sprintf("failed to find region for lb %s", lb.Name)) + return + } + self.SetStage("OnLoadbalancerStopComplete", nil) + if err := region.GetDriver().RequestStopLoadbalancer(ctx, self.GetUserCred(), lb, self); err != nil { + self.taskFail(ctx, lb, err.Error()) + } +} + +func (self *LoadbalancerStopTask) OnLoadbalancerStopComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_DISABLED, "") + db.OpsLog.LogEvent(lb, db.ACT_DISABLE, lb.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_DISABLE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerStopTask) OnLoadbalancerStopCompleteFailed(ctx context.Context, lb *models.SLoadbalancer, reason jsonutils.JSONObject) { + self.taskFail(ctx, lb, reason.String()) +} diff --git a/pkg/compute/tasks/loadbalancer_syncstatus_task.go b/pkg/compute/tasks/loadbalancer_syncstatus_task.go new file mode 100644 index 0000000000..2083ddec3b --- /dev/null +++ b/pkg/compute/tasks/loadbalancer_syncstatus_task.go @@ -0,0 +1,53 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type LoadbalancerSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(LoadbalancerSyncstatusTask{}) +} + +func (self *LoadbalancerSyncstatusTask) taskFail(ctx context.Context, lb *models.SLoadbalancer, reason string) { + lb.SetStatus(self.GetUserCred(), models.LB_STATUS_UNKNOWN, reason) + db.OpsLog.LogEvent(lb, db.ACT_SYNC_STATUS, reason, self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_SYNC_STATUS, reason, self.UserCred, false) + notifyclient.NotifySystemError(lb.Id, lb.Name, models.LB_SYNC_CONF_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *LoadbalancerSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lb := obj.(*models.SLoadbalancer) + region := lb.GetRegion() + if region == nil { + self.taskFail(ctx, lb, fmt.Sprintf("failed to find region for lb %s", lb.Name)) + return + } + self.SetStage("OnLoadbalancerSyncstatusComplete", nil) + if err := region.GetDriver().RequestSyncstatusLoadbalancer(ctx, self.GetUserCred(), lb, self); err != nil { + self.taskFail(ctx, lb, err.Error()) + } +} + +func (self *LoadbalancerSyncstatusTask) OnLoadbalancerSyncstatusComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(lb, db.ACT_SYNC_STATUS, lb.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLog(lb, logclient.ACT_SYNC_STATUS, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *LoadbalancerSyncstatusTask) OnLoadbalancerSyncstatusCompleteFailed(ctx context.Context, lb *models.SLoadbalancer, reason jsonutils.JSONObject) { + self.taskFail(ctx, lb, reason.String()) +} diff --git a/pkg/compute/tasks/network_delete_task.go b/pkg/compute/tasks/network_delete_task.go index bee05a2922..58c0532d3c 100644 --- a/pkg/compute/tasks/network_delete_task.go +++ b/pkg/compute/tasks/network_delete_task.go @@ -20,7 +20,7 @@ func init() { } func (self *NetworkDeleteTask) taskFailed(ctx context.Context, network *models.SNetwork, err error) { - log.Errorf("network create task fail: %s", err) + log.Errorf("network delete task fail: %s", err) network.SetStatus(self.UserCred, models.NETWORK_STATUS_DELETE_FAILED, err.Error()) db.OpsLog.LogEvent(network, db.ACT_ALLOCATE_FAIL, err.Error(), self.UserCred) self.SetStageFailed(ctx, err.Error()) diff --git a/pkg/compute/tasks/storage_cache_image_task.go b/pkg/compute/tasks/storage_cache_image_task.go index e379f6f2d9..b6324cf8b0 100644 --- a/pkg/compute/tasks/storage_cache_image_task.go +++ b/pkg/compute/tasks/storage_cache_image_task.go @@ -24,7 +24,23 @@ func (self *StorageCacheImageTask) OnInit(ctx context.Context, obj db.IStandalon // isForce := jsonutils.QueryBoolean(self.Params, "is_force", false) storageCache := obj.(*models.SStoragecache) - scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId) + + // first check if the storageCache reach cache limit + if storageCache.IsReachCapacityLimit(imageId) { + self.SetStage("OnRelinquishLeastUsedCachedImageComplete", nil) + storageCache.StartRelinquishLeastUsedCachedImageTask(ctx, self.UserCred, imageId, self.GetTaskId()) + } else { + self.OnRelinquishLeastUsedCachedImageComplete(ctx, obj, data) + } +} + +func (self *StorageCacheImageTask) OnRelinquishLeastUsedCachedImageComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + imageId, _ := self.Params.GetString("image_id") + // isForce := jsonutils.QueryBoolean(self.Params, "is_force", false) + + storageCache := obj.(*models.SStoragecache) + + scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId, "") if scimg.Status != models.CACHED_IMAGE_STATUS_READY { scimg.SetStatus(self.UserCred, models.CACHED_IMAGE_STATUS_CACHING, "storage_cache_image_task") } @@ -50,7 +66,7 @@ func (self *StorageCacheImageTask) OnImageCacheComplete(ctx context.Context, obj func (self *StorageCacheImageTask) OnImageCacheCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { storageCache := obj.(*models.SStoragecache) imageId, _ := self.Params.GetString("image_id") - scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId) + scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId, "") err := fmt.Errorf(data.String()) extImgId, _ := data.GetString("image_id") self.OnCacheFailed(ctx, storageCache, imageId, scimg, err, extImgId) @@ -70,7 +86,7 @@ func (self *StorageCacheImageTask) OnCacheFailed(ctx context.Context, cache *mod func (self *StorageCacheImageTask) OnCacheSucc(ctx context.Context, cache *models.SStoragecache, data *jsonutils.JSONDict) { imageId, _ := self.Params.GetString("image_id") - scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, cache.Id, imageId) + scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, cache.Id, imageId, "") extImgId, _ := data.GetString("image_id") scimg.SetStatus(self.UserCred, models.CACHED_IMAGE_STATUS_READY, "cached") diff --git a/pkg/compute/tasks/storage_uncache_image_task.go b/pkg/compute/tasks/storage_uncache_image_task.go index 63020d977a..436e241bb5 100644 --- a/pkg/compute/tasks/storage_uncache_image_task.go +++ b/pkg/compute/tasks/storage_uncache_image_task.go @@ -23,20 +23,21 @@ func init() { func (self *StorageUncacheImageTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { imageId, _ := self.Params.GetString("image_id") - isForce := jsonutils.QueryBoolean(self.Params, "is_force", false) + // isForce := jsonutils.QueryBoolean(self.Params, "is_force", false) + isPurge := jsonutils.QueryBoolean(self.Params, "is_purge", false) storageCache := obj.(*models.SStoragecache) db.OpsLog.LogEvent(storageCache, db.ACT_UNCACHING_IMAGE, imageId, self.UserCred) - scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId) + scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId, "") if scimg == nil || len(scimg.Path) == 0 { // "image is not cached on this storage" self.OnImageUncacheComplete(ctx, storageCache, nil) } - if isForce { + if isPurge { self.OnImageUncacheComplete(ctx, obj, data) return } @@ -85,7 +86,7 @@ func (self *StorageUncacheImageTask) OnImageUncacheComplete(ctx context.Context, storageCache := obj.(*models.SStoragecache) imageId, _ := self.Params.GetString("image_id") - scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId) + scimg := models.StoragecachedimageManager.Register(ctx, self.UserCred, storageCache.Id, imageId, "") if scimg != nil { scimg.Detach(ctx, self.UserCred) } diff --git a/pkg/hostimage/host_image_service.go b/pkg/hostimage/host_image_service.go index 1b04b596be..1dd1855af3 100644 --- a/pkg/hostimage/host_image_service.go +++ b/pkg/hostimage/host_image_service.go @@ -130,7 +130,7 @@ func getImage(ctx context.Context, w http.ResponseWriter, r *http.Request) { httperrors.GeneralServerError(w, err) return } - defer f.Close() // Remenber close fd + defer f.Close() endPos = f.Length() - 1 reqRange := r.Header.Get("Range") @@ -224,7 +224,7 @@ func getImageMeta(ctx context.Context, w http.ResponseWriter, r *http.Request) { httperrors.GeneralServerError(w, err) return } - defer f.Close() // Remenber close fd + defer f.Close() w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Length())) w.Header().Set("Content-Type", "application/octet-stream") diff --git a/pkg/hostman/diskhandlers/diskhandler.go b/pkg/hostman/diskhandlers/diskhandler.go new file mode 100644 index 0000000000..db5304c813 --- /dev/null +++ b/pkg/hostman/diskhandlers/diskhandler.go @@ -0,0 +1,233 @@ +package diskhandlers + +import ( + "context" + "fmt" + "net/http" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/util/regutils" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon/workmanager" + "yunion.io/x/onecloud/pkg/hostman/guestman" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/auth" +) + +var ( + keyWords = []string{"disks"} + actionFuncs = map[string]actionFunc{ + "create": diskCreate, + "delete": diskDelete, + "resize": diskResize, + "save-prepare": diskSavePrepare, + "reset": diskReset, + // "snapshot": diskSnapshot, + "cleanup-snapshots": diskCleanupSnapshots, + } +) + +type actionFunc func(context.Context, storageman.IStorage, string, storageman.IDisk, jsonutils.JSONObject) (interface{}, error) + +func AddDiskHandler(prefix string, app *appsrv.Application) { + for _, keyWord := range keyWords { + for _, seg := range []string{"iso_cache", "image_cache"} { + app.AddHandler("POST", + fmt.Sprintf("%s/%s/%s", prefix, keyWord, seg), + auth.Authenticate(perfetchImageCache)) + + app.AddHandler("DELETE", + fmt.Sprintf("%s/%s/%s", prefix, keyWord, seg), + auth.Authenticate(deleteImageCache)) + } + + app.AddHandler("POST", + fmt.Sprintf("%s/%s//upload", prefix, keyWord), + auth.Authenticate(saveToGlance)) + + app.AddHandler("POST", + fmt.Sprintf("%s/%s///", prefix, keyWord), + auth.Authenticate(perfomrDiskActions)) + } +} + +func performImageCache( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + performAction string, +) { + _, _, body := appsrv.FetchEnv(ctx, w, r) + + disk, err := body.Get("disk") + if err != nil { + httperrors.MissingParameterError(w, "disk") + return + } + scId, err := disk.GetString("storagecache_id") + if err != nil { + httperrors.MissingParameterError(w, "disk") + return + } + storagecache := storageman.GetManager().GetStoragecacheById(scId) + if storagecache == nil { + httperrors.NotFoundError(w, "Storagecache %s not found", scId) + return + } + + var performTask workmanager.DelayTaskFunc + if performAction == "perfetch" { + performTask = storagecache.PrefetchImageCache + } else { + performTask = storagecache.DeleteImageCache + } + + hostutils.DelayTask(ctx, performTask, disk) + hostutils.ResponseOk(ctx, w) +} + +func perfetchImageCache(ctx context.Context, w http.ResponseWriter, r *http.Request) { + performImageCache(ctx, w, r, "perfetch") +} + +func deleteImageCache(ctx context.Context, w http.ResponseWriter, r *http.Request) { + performImageCache(ctx, w, r, "delete") + +} + +func saveToGlance(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + var ( + storageId = params[""] + diskInfo, _ = body.Get("disk") + ) + storage := storageman.GetManager().GetStorage(storageId) + if storage == nil { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Storage %s not found", storageId)) + return + } + if diskInfo == nil { + hostutils.Response(ctx, w, httperrors.NewMissingParameterError("disk")) + return + } + + hostutils.DelayTaskWithoutReqctx(ctx, storage.SaveToGlance, diskInfo) + hostutils.ResponseOk(ctx, w) +} + +func perfomrDiskActions(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + if body == nil { + body = jsonutils.NewDict() + } + + var ( + storageId = params[""] // seg1 + action = params[""] // seg2 + diskId = params[""] // seg3 + ) + if !regutils.MatchUUID(storageId) { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Not found")) + return + } + storage := storageman.GetManager().GetStorage(storageId) + if storage == nil { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Storage %s not found", storageId)) + return + } + disk := storage.GetDiskById(diskId) + + if f, ok := actionFuncs[action]; !ok { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Not found")) + } else { + res, err := f(ctx, storage, diskId, disk, body) + if err != nil { + hostutils.Response(ctx, w, err) + } else if res != nil { + hostutils.Response(ctx, w, res) + } else { + hostutils.ResponseOk(ctx, w) + } + } +} + +func diskCreate(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { + diskInfo, err := body.Get("disk") + if err != nil { + return nil, httperrors.NewMissingParameterError("disk") + } + hostutils.DelayTask(ctx, storage.CreateDiskByDiskinfo, + &storageman.SDiskCreateByDiskinfo{diskId, disk, diskInfo, storage}) + return nil, nil +} + +func diskDelete(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { + if disk != nil { + hostutils.DelayTask(ctx, disk.Delete, nil) + } else { + hostutils.DelayTask(ctx, nil, nil) + } + return nil, nil +} + +func diskResize(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { + diskInfo, err := body.Get("disk") + if err != nil { + return nil, httperrors.NewMissingParameterError("disk") + } + serverId, _ := diskInfo.GetString("server_id") + if len(serverId) > 0 && guestman.GetGuestManager().Status(serverId) == "running" { + sizeMb, _ := diskInfo.Int("size") + return guestman.GetGuestManager().OnlineResizeDisk(ctx, serverId, diskId, sizeMb) + } else { + hostutils.DelayTask(ctx, disk.Resize, diskInfo) + return nil, nil + } +} + +func diskSavePrepare(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { + diskInfo, err := body.Get("disk") + if err != nil { + return nil, httperrors.NewMissingParameterError("disk") + } + hostutils.DelayTask(ctx, disk.PrepareSaveToGlance, diskInfo) + return nil, nil +} + +func diskReset(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { + snapshotId, err := body.GetString("snapshot_id") + if err != nil { + return nil, httperrors.NewMissingParameterError("snapshot_id") + } + outOfChain, err := body.Bool("out_of_chain") + if err != nil { + return nil, httperrors.NewMissingParameterError("out_of_chain") + } + hostutils.DelayTask(ctx, disk.ResetFromSnapshot, &storageman.SDiskReset{snapshotId, outOfChain}) + return nil, nil +} + +// func diskSnapshot(ctx context.Context, storage IStorage, diskId string, disk IDisk, body jsonutils.JSONObject) (interface{}, error) { +// snapshotId, err := body.GetString("snapshot_id") +// if err != nil { +// return nil, httperrors.NewMissingParameterError("snapshot_id") +// } +// hostutils.DelayTask(ctx, disk.CreateSnapshot(snapshotId), snapshotId) +// return nil, nil +// } + +func diskCleanupSnapshots(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { + convertSnapshots, err := body.GetArray("convert_snapshots") + if err != nil { + return nil, httperrors.NewMissingParameterError("convert_snapshots") + } + deleteSnapshots, err := body.GetArray("delete_snapshots") + if err != nil { + return nil, httperrors.NewMissingParameterError("delete_snapshots") + } + hostutils.DelayTask(ctx, disk.CleanupSnapshots, &storageman.SDiskCleanupSnapshots{convertSnapshots, deleteSnapshots}) + return nil, nil +} diff --git a/pkg/hostman/diskhandlers/doc.go b/pkg/hostman/diskhandlers/doc.go new file mode 100644 index 0000000000..b0bbdf291d --- /dev/null +++ b/pkg/hostman/diskhandlers/doc.go @@ -0,0 +1 @@ +package diskhandlers // import "yunion.io/x/onecloud/pkg/hostman/diskhandlers" diff --git a/pkg/hostman/doc.go b/pkg/hostman/doc.go new file mode 100644 index 0000000000..82256403f9 --- /dev/null +++ b/pkg/hostman/doc.go @@ -0,0 +1 @@ +package hostman // import "yunion.io/x/onecloud/pkg/hostman" diff --git a/pkg/hostman/downloader/doc.go b/pkg/hostman/downloader/doc.go new file mode 100644 index 0000000000..a95a3eb98d --- /dev/null +++ b/pkg/hostman/downloader/doc.go @@ -0,0 +1 @@ +package downloader // import "yunion.io/x/onecloud/pkg/hostman/downloader" diff --git a/pkg/hostman/downloader/downloader.go b/pkg/hostman/downloader/downloader.go new file mode 100644 index 0000000000..e2a7dad41d --- /dev/null +++ b/pkg/hostman/downloader/downloader.go @@ -0,0 +1,115 @@ +package downloader + +import ( + "compress/zlib" + "io" + "net/http" + "os" + "time" + + "yunion.io/x/log" +) + +const ( + CHUNK_SIZE = 1024 * 8 + DEFAULT_RATE_LIMIT = 50 + COMPRESS_LEVEL = 1 +) + +type SDownloadProvider struct { + w http.ResponseWriter + rateLimit int + compress bool +} + +func NewDownloadProvider(w http.ResponseWriter, compress bool, rateLimit int) *SDownloadProvider { + if rateLimit <= 0 { + rateLimit = DEFAULT_RATE_LIMIT + } + return &SDownloadProvider{w, rateLimit, compress} +} + +func (d *SDownloadProvider) Start( + prepareDownload func() error, onDownloadComplete func(), + downloadFilePath string, headers http.Header, +) error { + if prepareDownload != nil { + if err := prepareDownload(); err != nil { + log.Errorln(err) + return err + } + } + if headers.Get("Content-Type") == "" { + headers.Set("Content-Type", "application/octet-stream") + } + for k := range headers { + d.w.Header().Add(k, headers.Get(k)) + } + + log.Infof("Downloader Start Transfer %s, compress %t", downloadFilePath, d.compress) + fi, err := os.Open(downloadFilePath) + if err != nil { + log.Errorln(err) + return err + } + defer fi.Close() + + var ( + end = false + chunk = make([]byte, CHUNK_SIZE) + writer io.Writer = d.w + startTime = time.Now() + sendBytes = 0 + writeChunk []byte + ) + + if d.compress { + zw, err := zlib.NewWriterLevel(d.w, COMPRESS_LEVEL) + if err != nil { + log.Errorln(err) + return err + } + writer = zw + defer zw.Close() + defer zw.Flush() // it's cool + } + + for !end { + size, err := fi.Read(chunk) + if err != nil { + if err != io.EOF { + log.Errorln(err) + return err + } else { + end = true + } + } + + writeChunk = chunk[:size] + if size, err = writer.Write(writeChunk); err != nil { + log.Errorln(err) + return err + } else { + sendBytes += size + timeDur := time.Now().Sub(startTime) + exceptDur := float64(sendBytes) / 1000.0 / 1000.0 / float64(d.rateLimit) + if exceptDur > timeDur.Seconds() { + time.Sleep(time.Duration(exceptDur-timeDur.Seconds()) * time.Second) + } + } + } + + // if d.compress { + // zw := writer.(*zlib.Writer) + // zw.Flush() + // } + + sendMb := float64(sendBytes) / 1000.0 / 1000.0 + timeDur := time.Now().Sub(startTime) + log.Infof("Send data: %fMB rate: %fMB/sec", sendMb, sendMb/timeDur.Seconds()) + + if onDownloadComplete != nil { + onDownloadComplete() + } + return nil +} diff --git a/pkg/hostman/downloader/downloadhandler.go b/pkg/hostman/downloader/downloadhandler.go new file mode 100644 index 0000000000..dfa964aa69 --- /dev/null +++ b/pkg/hostman/downloader/downloadhandler.go @@ -0,0 +1,185 @@ +package downloader + +import ( + "context" + "fmt" + "net/http" + "time" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/fileutils2" +) + +var ( + keyWords = []string{"download"} + streamingWorkerMan *appsrv.SWorkerManager +) + +func init() { + streamingWorkerMan = appsrv.NewWorkerManager("streaming_worker", 20, 1024, false) +} + +func AddDownloadHandler(prefix string, app *appsrv.Application) { + for _, kerword := range keyWords { + hi := app.AddHandler2("GET", fmt.Sprintf("%s/%s//", prefix, kerword), + auth.Authenticate(download), nil, "download", nil) + customizeHandlerInfo(hi) + + hi = app.AddHandler2("GET", fmt.Sprintf("%s/%s/disks//", + prefix, kerword), auth.Authenticate(diskDownload), nil, "disk_download", nil) + customizeHandlerInfo(hi) + + hi = app.AddHandler2("GET", fmt.Sprintf( + "%s/%s/snapshots///", + prefix, kerword), auth.Authenticate(snapshotDownload), + nil, "snapshot_download", nil) + customizeHandlerInfo(hi) + + app.AddHandler("HEAD", fmt.Sprintf("%s/%s/disks//", + prefix, kerword), auth.Authenticate(diskHead)) + app.AddHandler("HEAD", + fmt.Sprintf("%s/%s/snapshots///", + prefix, kerword), auth.Authenticate(snapshotHead)) + } +} + +func customizeHandlerInfo(info *appsrv.SHandlerInfo) { + switch info.GetName(nil) { + case "disk_download", "download", "snapshot_download": + info.SetProcessTimeout(time.Minute * 30).SetWorkerManager(streamingWorkerMan) + } +} + +func isCompress(r *http.Request) bool { + return r.Header.Get("X-Compress-Content") == "zlib" +} + +func download(ctx context.Context, w http.ResponseWriter, r *http.Request) { + var ( + params, _, _ = appsrv.FetchEnv(ctx, w, r) + id = params[""] + action = params[""] + rateLimit = options.HostOptions.BandwidthLimit + compress = isCompress(r) + ) + + switch action { + case "images": + hand := NewImageCacheDownloadProvider(w, compress, rateLimit, id) + if !fileutils2.Exists(hand.downloadFilePath()) { + httperrors.NotFoundError(w, "Image cache %s not found", id) + } else { + if err := hand.Start(); err != nil { + hostutils.Response(ctx, w, err) + } + } + case "servers": + hand := NewGuestDownloadProvider(w, compress, rateLimit, id) + if !fileutils2.Exists(hand.fullPath()) { + httperrors.NotFoundError(w, "Guest %s not found", id) + } else { + if err := hand.Start(); err != nil { + hostutils.Response(ctx, w, err) + } + } + default: + hostutils.Response(ctx, w, httperrors.NewNotFoundError("%s Not found", action)) + } +} + +func diskPrecheck( + ctx context.Context, w http.ResponseWriter, r *http.Request, +) (storageman.IDisk, error) { + var ( + params, _, _ = appsrv.FetchEnv(ctx, w, r) + storageId = params[""] + diskId = params[""] + ) + storage := storageman.GetManager().GetStorage(storageId) + if storage == nil { + return nil, httperrors.NewNotFoundError("Storage %s not found", storageId) + } + disk := storage.GetDiskById(diskId) + if disk == nil { + return nil, httperrors.NewNotFoundError("Disk %s not found", diskId) + } + return disk, nil +} + +func diskDownload(ctx context.Context, w http.ResponseWriter, r *http.Request) { + disk, err := diskPrecheck(ctx, w, r) + if err != nil { + hostutils.Response(ctx, w, err) + } else { + var compress = isCompress(r) + hand := NewImageDownloadProvider(w, + compress, options.HostOptions.BandwidthLimit, disk, "") + if err := hand.Start(); err != nil { + hostutils.Response(ctx, w, err) + } + } +} + +func diskHead(ctx context.Context, w http.ResponseWriter, r *http.Request) { + disk, err := diskPrecheck(ctx, w, r) + if err != nil { + hostutils.Response(ctx, w, err) + } else { + var compress = isCompress(r) + hand := NewImageDownloadProvider(w, + compress, options.HostOptions.BandwidthLimit, disk, "") + if err := hand.HandlerHead(); err != nil { + hostutils.Response(ctx, w, err) + } + } +} + +func snapshotPrecheck( + ctx context.Context, w http.ResponseWriter, r *http.Request, +) (string, error) { + var ( + params, _, _ = appsrv.FetchEnv(ctx, w, r) + storageId = params[""] + diskId = params[""] + snapshotId = params[""] + ) + + storage := storageman.GetManager().GetStorage(storageId) + if storage == nil { + return "", httperrors.NewNotFoundError("Storage %s not found", storageId) + } + return storage.GetSnapshotPathByIds(diskId, snapshotId), nil +} + +func snapshotDownload(ctx context.Context, w http.ResponseWriter, r *http.Request) { + snapshotPath, err := snapshotPrecheck(ctx, w, r) + if err != nil { + hostutils.Response(ctx, w, err) + } else { + var compress = isCompress(r) + hand := NewSnapshotDownloadProvider(w, + compress, options.HostOptions.BandwidthLimit, snapshotPath) + if err := hand.Start(); err != nil { + hostutils.Response(ctx, w, err) + } + } +} + +func snapshotHead(ctx context.Context, w http.ResponseWriter, r *http.Request) { + snapshotPath, err := snapshotPrecheck(ctx, w, r) + if err != nil { + hostutils.Response(ctx, w, err) + } else { + var compress = isCompress(r) + hand := NewSnapshotDownloadProvider(w, + compress, options.HostOptions.BandwidthLimit, snapshotPath) + if err := hand.HandlerHead(); err != nil { + hostutils.Response(ctx, w, err) + } + } +} diff --git a/pkg/hostman/downloader/guest_downloader.go b/pkg/hostman/downloader/guest_downloader.go new file mode 100644 index 0000000000..ac071bd724 --- /dev/null +++ b/pkg/hostman/downloader/guest_downloader.go @@ -0,0 +1,56 @@ +package downloader + +import ( + "net/http" + "os" + "path" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/tarutils" +) + +type SGuestDownloadProvider struct { + *SDownloadProvider + serverId string +} + +func NewGuestDownloadProvider( + w http.ResponseWriter, compress bool, rateLimit int, sid string, +) *SGuestDownloadProvider { + return &SGuestDownloadProvider{ + SDownloadProvider: NewDownloadProvider(w, compress, rateLimit), + serverId: sid, + } +} + +func (s *SGuestDownloadProvider) fullPath() string { + return path.Join(options.HostOptions.ServersPath, s.serverId) +} + +func (s *SGuestDownloadProvider) getHeaders() http.Header { + hdrs := http.Header{} + hdrs.Set("X-Image-Meta-Disk_format", "tar") + return hdrs +} + +func (i *SGuestDownloadProvider) onDownloadComplete() { + if fileutils2.Exists(i.downloadFilePath()) { + os.Remove(i.downloadFilePath()) + } +} + +func (s *SGuestDownloadProvider) downloadFilePath() string { + return s.fullPath() + ".tar" +} + +func (s *SGuestDownloadProvider) prepareDownload() error { + log.Infof("Compress %s to %s", s.fullPath(), s.downloadFilePath()) + return tarutils.TarSparseFile(s.fullPath(), s.downloadFilePath()) +} + +func (s *SGuestDownloadProvider) Start() error { + return s.SDownloadProvider.Start(s.prepareDownload, + s.onDownloadComplete, s.downloadFilePath(), s.getHeaders()) +} diff --git a/pkg/hostman/downloader/image_downloader.go b/pkg/hostman/downloader/image_downloader.go new file mode 100644 index 0000000000..7e44c1d5d8 --- /dev/null +++ b/pkg/hostman/downloader/image_downloader.go @@ -0,0 +1,96 @@ +package downloader + +import ( + "net/http" + "os" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/qemuimg" + "yunion.io/x/onecloud/pkg/util/tarutils" + "yunion.io/x/pkg/utils" +) + +type SImageDownloadProvider struct { + *SDownloadProvider + disk storageman.IDisk + compressFormat string +} + +func NewImageDownloadProvider(w http.ResponseWriter, compress bool, rateLimit int, disk storageman.IDisk, compressFormat string) *SImageDownloadProvider { + return &SImageDownloadProvider{ + SDownloadProvider: NewDownloadProvider(w, compress, rateLimit), + disk: disk, + compressFormat: compressFormat, + } +} + +func (i *SImageDownloadProvider) fullPath() string { + return i.disk.GetPath() +} + +func (i *SImageDownloadProvider) downloadFilePath() string { + if utils.IsInStringArray(i.compressFormat, []string{"qcow2", "tar"}) { + return i.fullPath() + "." + i.compressFormat + } else { + return i.fullPath() + } +} + +func (i *SImageDownloadProvider) prepareDownload() error { + if i.fullPath() != i.downloadFilePath() { + log.Infof("Compress %s %s to %s", i.compressFormat, i.fullPath(), i.downloadFilePath()) + } + + switch i.compressFormat { + case "qcow2": + img, err := qemuimg.NewQemuImage(i.fullPath()) + if err != nil { + return err + } + _, err = img.CloneQcow2(i.downloadFilePath(), true) + return err + case "tar": + return tarutils.TarSparseFile(i.fullPath(), i.downloadFilePath()) + default: + return nil + } +} + +func (i *SImageDownloadProvider) onDownloadComplete() { + if i.downloadFilePath() != i.fullPath() && fileutils2.Exists(i.downloadFilePath()) { + os.Remove(i.downloadFilePath()) + } +} + +func (i *SImageDownloadProvider) getHeaders() http.Header { + hdrs := http.Header{} + if utils.IsInStringArray(i.compressFormat, []string{"qcow2", "tar"}) { + hdrs.Set("X-Image-Meta-Disk_format", i.compressFormat) + } + return hdrs +} + +func (i *SImageDownloadProvider) Start() error { + return i.SDownloadProvider.Start(i.prepareDownload, i.onDownloadComplete, + i.downloadFilePath(), i.getHeaders()) +} + +func (i *SImageDownloadProvider) HandlerHead() error { + headers := i.getHeaders() + if len(i.compressFormat) > 0 { + headers.Set("X-Image-Meta-Checksum", "error") + } else { + checksum, err := fileutils2.MD5(i.fullPath()) + if err != nil { + return err + } + headers.Set("X-Image-Meta-Checksum", checksum) + } + for k := range headers { + i.w.Header().Add(k, headers.Get(k)) + } + i.w.WriteHeader(200) + return nil +} diff --git a/pkg/hostman/downloader/imagecache_downloader.go b/pkg/hostman/downloader/imagecache_downloader.go new file mode 100644 index 0000000000..5afd3238ce --- /dev/null +++ b/pkg/hostman/downloader/imagecache_downloader.go @@ -0,0 +1,35 @@ +package downloader + +import ( + "net/http" + "path" + + "yunion.io/x/onecloud/pkg/hostman/storageman" +) + +type SImageCacheDownloadProvider struct { + *SDownloadProvider + imageId string +} + +func NewImageCacheDownloadProvider( + w http.ResponseWriter, compress bool, rateLimit int, imageId string, +) *SImageCacheDownloadProvider { + return &SImageCacheDownloadProvider{ + SDownloadProvider: NewDownloadProvider(w, compress, rateLimit), + imageId: imageId, + } +} + +func (s *SImageCacheDownloadProvider) getHeaders() http.Header { + return http.Header{} +} + +func (s *SImageCacheDownloadProvider) downloadFilePath() string { + return path.Join( + storageman.GetManager().LocalStorageImagecacheManager.GetPath(), s.imageId) +} + +func (s *SImageCacheDownloadProvider) Start() error { + return s.SDownloadProvider.Start(nil, nil, s.downloadFilePath(), s.getHeaders()) +} diff --git a/pkg/hostman/downloader/snapshot_downloader.go b/pkg/hostman/downloader/snapshot_downloader.go new file mode 100644 index 0000000000..30409bb27c --- /dev/null +++ b/pkg/hostman/downloader/snapshot_downloader.go @@ -0,0 +1,48 @@ +package downloader + +import ( + "net/http" + + "yunion.io/x/onecloud/pkg/util/fileutils2" +) + +type SSnapshotDownloadProvider struct { + *SDownloadProvider + snapshotPath string +} + +func NewSnapshotDownloadProvider( + w http.ResponseWriter, compress bool, rateLimit int, snapshotPath string, +) *SSnapshotDownloadProvider { + return &SSnapshotDownloadProvider{ + SDownloadProvider: NewDownloadProvider(w, compress, rateLimit), + snapshotPath: snapshotPath, + } +} + +func (s *SSnapshotDownloadProvider) getHeaders() http.Header { + hdrs := http.Header{} + hdrs.Set("X-Image-Meta-Disk_format", "") + return hdrs +} + +func (s *SSnapshotDownloadProvider) HandlerHead() error { + headers := s.getHeaders() + if fileutils2.Exists(s.snapshotPath) { + chksum, err := fileutils2.MD5(s.snapshotPath) + if err != nil { + return err + } + headers.Set("X-Image-Meta-Checksum", chksum) + } + s.w.WriteHeader(200) + return nil +} + +func (s *SSnapshotDownloadProvider) downloadFilePath() string { + return s.snapshotPath +} + +func (s *SSnapshotDownloadProvider) Start() error { + return s.SDownloadProvider.Start(nil, nil, s.downloadFilePath(), s.getHeaders()) +} diff --git a/pkg/hostman/guestfs/core.go b/pkg/hostman/guestfs/core.go new file mode 100644 index 0000000000..982b50471a --- /dev/null +++ b/pkg/hostman/guestfs/core.go @@ -0,0 +1,231 @@ +package guestfs + +import ( + "fmt" + "math/rand" + "path/filepath" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" +) + +type SDeployInfo struct { + publicKey *sshkeys.SSHKeys + deploys []jsonutils.JSONObject + password string + isInit bool + enableTty bool + defaultRootUser bool + windowsDefaultAdminUser bool +} + +func NewDeployInfo( + publicKey *sshkeys.SSHKeys, + deploys []jsonutils.JSONObject, + password string, + isInit bool, + enableTty bool, + defaultRootUser bool, + windowsDefaultAdminUser bool, +) *SDeployInfo { + return &SDeployInfo{ + publicKey: publicKey, + deploys: deploys, + password: password, + isInit: isInit, + enableTty: enableTty, + defaultRootUser: defaultRootUser, + windowsDefaultAdminUser: windowsDefaultAdminUser, + } +} + +func (d *SDeployInfo) String() string { + return fmt.Sprintf("deplys: %s, password %s, isInit: %v, enableTty: %v, defaultRootUser: %v", + d.deploys, d.password, d.isInit, d.enableTty, d.defaultRootUser) +} + +func DetectRootFs(part fsdriver.IDiskPartition) fsdriver.IRootFsDriver { + for _, newDriverFunc := range fsdriver.GetRootfsDrivers() { + d := newDriverFunc(part) + if testRootfs(d) { + return d + } + } + return nil +} + +func testRootfs(d fsdriver.IRootFsDriver) bool { + caseInsensitive := d.IsFsCaseInsensitive() + for _, rd := range d.RootSignatures() { + if !d.GetPartition().Exists(rd, caseInsensitive) { + log.Infof("[%s] test root fs: %s not exists", d, rd) + return false + } + } + for _, rd := range d.RootExcludeSignatures() { + if d.GetPartition().Exists(rd, caseInsensitive) { + log.Infof("[%s] test root fs: %s exists, test failed", d, rd) + return false + } + } + return true +} + +func DeployFiles(rootfs fsdriver.IRootFsDriver, deploys []jsonutils.JSONObject) error { + caseInsensitive := rootfs.IsFsCaseInsensitive() + for _, deploy := range deploys { + var modAppend = false + if action, _ := deploy.GetString("action"); action == "append" { + modAppend = true + } + sPath, err := deploy.GetString("path") + if err != nil { + return err + } + dirname := filepath.Dir(sPath) + if !rootfs.GetPartition().Exists(sPath, caseInsensitive) { + modeRWXOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + err := rootfs.GetPartition().Mkdir(dirname, modeRWXOwner, caseInsensitive) + if err != nil { + return err + } + } + if content, err := deploy.GetString("content"); err != nil { + err := rootfs.GetPartition().FilePutContents(sPath, content, modAppend, caseInsensitive) + if err != nil { + return err + } + } + } + return nil +} + +func DeployGuestFs( + rootfs fsdriver.IRootFsDriver, + guestDesc *jsonutils.JSONDict, + deployInfo *SDeployInfo, +) (jsonutils.JSONObject, error) { + var ret = jsonutils.NewDict() + var ips = make([]string, 0) + var err error + + hn, _ := guestDesc.GetString("name") + domain, _ := guestDesc.GetString("domain") + gid, _ := guestDesc.GetString("uuid") + nics, _ := guestDesc.GetArray("nics") + + partition := rootfs.GetPartition() + releaseInfo := rootfs.GetReleaseInfo(partition) + + for _, n := range nics { + ip, _ := n.GetString("ip") + var addr netutils.IPV4Addr + if addr, err = netutils.NewIPV4Addr(ip); err != nil { + return nil, fmt.Errorf("Fail to get ip addr from %s: %v", n.String(), err) + } + if netutils.IsPrivate(addr) { + ips = append(ips, ip) + } + } + if releaseInfo != nil { + ret.Set("distro", jsonutils.NewString(releaseInfo.Distro)) + if len(releaseInfo.Version) > 0 { + ret.Set("version", jsonutils.NewString(releaseInfo.Version)) + } + if len(releaseInfo.Arch) > 0 { + ret.Set("arch", jsonutils.NewString(releaseInfo.Arch)) + } + if len(releaseInfo.Language) > 0 { + ret.Set("language", jsonutils.NewString(releaseInfo.Language)) + } + } + ret.Set("os", jsonutils.NewString(rootfs.GetOs())) + + if IsPartitionReadonly(partition) { + return ret, nil + } + + if len(deployInfo.deploys) > 0 { + if err = DeployFiles(rootfs, deployInfo.deploys); err != nil { + return nil, fmt.Errorf("DeployFiles: %v", err) + } + } + if err = rootfs.DeployHostname(partition, hn, domain); err != nil { + return nil, fmt.Errorf("DeployHostname: %v", err) + } + if err = rootfs.DeployHosts(partition, hn, domain, ips); err != nil { + return nil, fmt.Errorf("DeployHosts: %v", err) + } + if err = rootfs.DeployNetworkingScripts(partition, nics); err != nil { + return nil, fmt.Errorf("DeployNetworkingScripts: %v", err) + } + if nicsStandby, e := guestDesc.GetArray("nics_standby"); e == nil { + if err = rootfs.DeployStandbyNetworkingScripts(partition, nics, nicsStandby); err != nil { + return nil, fmt.Errorf("DeployStandbyNetworkingScripts: %v", err) + } + } + if err = rootfs.DeployUdevSubsystemScripts(partition); err != nil { + return nil, fmt.Errorf("DeployUdevSubsystemScripts: %v", err) + } + if deployInfo.isInit { + disks, _ := guestDesc.GetArray("disks") + if err = rootfs.DeployFstabScripts(partition, disks); err != nil { + return nil, fmt.Errorf("DeployFstabScripts: %v", err) + } + } + if len(deployInfo.password) > 0 { + if account := rootfs.GetLoginAccount(partition, + deployInfo.defaultRootUser, deployInfo.windowsDefaultAdminUser); len(account) > 0 { + ret.Set("account", jsonutils.NewString(account)) + if err = rootfs.DeployPublicKey(partition, account, deployInfo.publicKey); err != nil { + return nil, fmt.Errorf("DeployPublicKey: %v", err) + } + var secret string + if secret, err = rootfs.ChangeUserPasswd(partition, account, gid, + deployInfo.publicKey.PublicKey, deployInfo.password); err != nil { + return nil, fmt.Errorf("ChangeUserPasswd: %v", err) + } + if len(secret) > 0 { + ret.Set("key", jsonutils.NewString(secret)) + } + } + } + + if err = rootfs.DeployYunionroot(partition, deployInfo.publicKey); err != nil { + return nil, fmt.Errorf("DeployYunionroot: %v", err) + } + if partition.SupportSerialPorts() { + if deployInfo.enableTty { + if err = rootfs.EnableSerialConsole(partition, ret); err != nil { + return nil, fmt.Errorf("EnableSerialConsole: %v", err) + } + } else { + if err = rootfs.DisableSerialConsole(partition); err != nil { + return nil, fmt.Errorf("DisableSerialConsole: %v", err) + } + } + if err = rootfs.CommitChanges(partition); err != nil { + return nil, fmt.Errorf("CommitChanges: %v", err) + } + } + + log.Debugf("Deploy finished, return: %s", ret.String()) + return ret, nil +} + +func IsPartitionReadonly(rootfs fsdriver.IDiskPartition) bool { + log.Infof("Test if read-only fs ...") + var filename = fmt.Sprintf("/.%f", rand.Float32()) + if err := rootfs.FilePutContents(filename, fmt.Sprintf("%f", rand.Float32()), false, false); err == nil { + rootfs.Remove(filename, false) + return false + } else { + log.Errorf("File system is readonly: %s", err) + return true + } +} diff --git a/pkg/hostman/guestfs/doc.go b/pkg/hostman/guestfs/doc.go new file mode 100644 index 0000000000..e94a19bbe9 --- /dev/null +++ b/pkg/hostman/guestfs/doc.go @@ -0,0 +1 @@ +package guestfs // import "yunion.io/x/onecloud/pkg/hostman/guestfs" diff --git a/pkg/hostman/guestfs/fsdriver/android.go b/pkg/hostman/guestfs/fsdriver/android.go new file mode 100644 index 0000000000..8389aa1153 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/android.go @@ -0,0 +1,9 @@ +package fsdriver + +type SAndroidRootFs struct { + *sGuestRootFsDriver +} + +//func NewAndroidRootFs(part IDiskPartition) IRootFsDriver { +//return &SAndroidRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/hostman/guestfs/fsdriver/base.go b/pkg/hostman/guestfs/fsdriver/base.go new file mode 100644 index 0000000000..37d127c048 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/base.go @@ -0,0 +1,157 @@ +package fsdriver + +import ( + "fmt" + "path" + "strings" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" +) + +type sGuestRootFsDriver struct { + rootFs IDiskPartition +} + +func newGuestRootFsDriver(rootFs IDiskPartition) *sGuestRootFsDriver { + return &sGuestRootFsDriver{ + rootFs: rootFs, + } +} + +func (d *sGuestRootFsDriver) GetPartition() IDiskPartition { + return d.rootFs +} + +func (d *sGuestRootFsDriver) RootExcludeSignatures() []string { + return []string{} +} + +func (d *sGuestRootFsDriver) IsFsCaseInsensitive() bool { + return false +} + +func (d *sGuestRootFsDriver) DeployYunionroot(rootfs IDiskPartition, pubkeys *sshkeys.SSHKeys) error { + return nil +} + +func (d *sGuestRootFsDriver) DeployUdevSubsystemScripts(rootfs IDiskPartition) error { + return nil +} + +func (d *sGuestRootFsDriver) DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error { + return nil +} + +func (d *sGuestRootFsDriver) DeployFstabScripts(_ IDiskPartition, _ []jsonutils.JSONObject) error { + return nil +} + +func (d *sGuestRootFsDriver) EnableSerialConsole(rootfs IDiskPartition, sysInfo *jsonutils.JSONDict) error { + return nil +} + +func (d *sGuestRootFsDriver) DisableSerialConsole(rootfs IDiskPartition) error { + return nil +} + +func (d *sGuestRootFsDriver) CommitChanges(rootfs IDiskPartition) error { + return nil +} + +type SReleaseInfo struct { + Distro string + Version string + Arch string + Language string +} + +func newReleaseInfo(distro, version, arch string) *SReleaseInfo { + return &SReleaseInfo{ + Distro: distro, + Version: version, + Arch: arch, + } +} + +func DeployAuthorizedKeys(rootFs IDiskPartition, usrDir string, pubkeys *sshkeys.SSHKeys, replace bool) error { + usrStat := rootFs.Stat(usrDir, false) + if usrStat != nil { + sshDir := path.Join(usrDir, ".ssh") + authFile := path.Join(sshDir, "authorized_keys") + modeRwxOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + modeRwOwner := syscall.S_IRUSR | syscall.S_IWUSR + fStat, _ := usrStat.Sys().(*syscall.Stat_t) + if !rootFs.Exists(sshDir, false) { + err := rootFs.Mkdir(sshDir, modeRwxOwner, false) + if err != nil { + log.Errorln(err) + return err + } + err = rootFs.Chown(sshDir, int(fStat.Uid), int(fStat.Gid), false) + if err != nil { + log.Errorln(err) + return err + } + } + var oldKeys = "" + if !replace { + bOldKeys, _ := rootFs.FileGetContents(authFile, false) + oldKeys = string(bOldKeys) + } + newKeys := MergeAuthorizedKeys(oldKeys, pubkeys) + if err := rootFs.FilePutContents(authFile, newKeys, false, false); err != nil { + return fmt.Errorf("Put keys to %s: %v", authFile, err) + } + log.Infof("after merge keys=====%s, put to: %s", newKeys, authFile) + if err := rootFs.Chown(authFile, int(fStat.Uid), int(fStat.Gid), false); err != nil { + return fmt.Errorf("Chown %s to uid: %d, gid: %d: %v", authFile, fStat.Uid, fStat.Gid, err) + } + if err := rootFs.Chmod(authFile, uint32(modeRwOwner), false); err != nil { + return fmt.Errorf("Chmod %s to %d error: %v", authFile, uint32(modeRwOwner), err) + } + } + return nil +} + +func MergeAuthorizedKeys(oldKeys string, pubkeys *sshkeys.SSHKeys) string { + var allkeys = make(map[string]string) + if len(oldKeys) > 0 { + for _, line := range strings.Split(oldKeys, "\n") { + line = strings.TrimSpace(line) + dat := strings.Split(line, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; !ok { + allkeys[dat[1]] = line + } + } + } + } + if len(pubkeys.DeletePublicKey) > 0 { + dat := strings.Split(pubkeys.DeletePublicKey, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; ok { + delete(allkeys, dat[1]) + } + } + } + for _, k := range []string{pubkeys.PublicKey, pubkeys.AdminPublicKey, pubkeys.ProjectPublicKey} { + if len(k) > 0 { + k = strings.TrimSpace(k) + dat := strings.Split(k, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; !ok { + allkeys[dat[1]] = k + } + } + } + } + var keys = make([]string, 0) + for _, val := range allkeys { + keys = append(keys, val) + } + return strings.Join(keys, "\n") +} diff --git a/pkg/hostman/guestfs/fsdriver/base_test.go b/pkg/hostman/guestfs/fsdriver/base_test.go new file mode 100644 index 0000000000..fc2b662f24 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/base_test.go @@ -0,0 +1,35 @@ +package fsdriver + +import ( + "testing" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" +) + +func TestMergeAuthorizedKeys(t *testing.T) { + type args struct { + oldKeys string + pubkeys *sshkeys.SSHKeys + } + tests := []struct { + name string + args args + want string + }{ + { + name: "MergeAuthorizedKeys", + args: args{ + oldKeys: "Test KEY", + pubkeys: &sshkeys.SSHKeys{}, + }, + want: "KEY", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := MergeAuthorizedKeys(tt.args.oldKeys, tt.args.pubkeys); got != tt.want { + t.Errorf("MergeAuthorizedKeys() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/hostman/guestfs/fsdriver/doc.go b/pkg/hostman/guestfs/fsdriver/doc.go new file mode 100644 index 0000000000..2220980417 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/doc.go @@ -0,0 +1 @@ +package fsdriver // import "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" diff --git a/pkg/hostman/guestfs/fsdriver/drivers.go b/pkg/hostman/guestfs/fsdriver/drivers.go new file mode 100644 index 0000000000..7ee33c1cc8 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/drivers.go @@ -0,0 +1,22 @@ +package fsdriver + +type newRootFsDriverFunc func(part IDiskPartition) IRootFsDriver + +var rootfsDrivers = make([]newRootFsDriverFunc, 0) + +func GetRootfsDrivers() []newRootFsDriverFunc { + return rootfsDrivers +} + +func init() { + linuxFsDrivers := []newRootFsDriverFunc{ + NewCentosRootFs, NewFedoraRootFs, NewRhelRootFs, + NewDebianRootFs, NewCirrosRootFs, NewCirrosNewRootFs, NewUbuntuRootFs, + //NewGentooRootFs, NewArchLinuxRootFs, NewOpenWrtRootFs, NewCoreOsRootFs, + } + rootfsDrivers = append(rootfsDrivers, linuxFsDrivers...) + //rootfsDrivers = append(rootfsDrivers, NewMacOSRootFs) + //rootfsDrivers = append(rootfsDrivers, NewEsxiRootFs) + rootfsDrivers = append(rootfsDrivers, NewWindowsRootFs) + //rootfsDrivers = append(rootfsDrivers, NewAndroidRootFs) +} diff --git a/pkg/hostman/guestfs/fsdriver/esxi.go b/pkg/hostman/guestfs/fsdriver/esxi.go new file mode 100644 index 0000000000..3c3ab98ea3 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/esxi.go @@ -0,0 +1,9 @@ +package fsdriver + +type SEsxiRootFs struct { + *sGuestRootFsDriver +} + +//func NewEsxiRootFs(part IDiskPartition) IRootFsDriver { +//return &SEsxiRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/hostman/guestfs/fsdriver/interface.go b/pkg/hostman/guestfs/fsdriver/interface.go new file mode 100644 index 0000000000..1224dc1048 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/interface.go @@ -0,0 +1,65 @@ +package fsdriver + +import ( + "os" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" +) + +type IDiskPartition interface { + GetLocalPath(sPath string, caseInsensitive bool) string + FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) + FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error + Exists(sPath string, caseInsensitive bool) bool + Chown(sPath string, uid, gid int, caseInsensitive bool) error + Chmod(sPath string, mode uint32, caseInsensitive bool) error + UserAdd(user string, caseInsensitive bool) error + Stat(sPath string, caseInsensitive bool) os.FileInfo + + Passwd(account, password string, caseInsensitive bool) error + Mkdir(sPath string, mode int, caseInsensitive bool) error + ListDir(sPath string, caseInsensitive bool) []string + Remove(path string, caseInsensitive bool) + Cleandir(dir string, keepdir, caseInsensitive bool) error + Zerofiles(dir string, caseInsensitive bool) error + SupportSerialPorts() bool + + Mount() bool + Umount() bool +} + +type IRootFsDriver interface { + GetPartition() IDiskPartition + GetName() string + String() string + + IsFsCaseInsensitive() bool + RootSignatures() []string + RootExcludeSignatures() []string + GetReleaseInfo(IDiskPartition) *SReleaseInfo + GetOs() string + DeployHostname(part IDiskPartition, hn, domain string) error + DeployHosts(part IDiskPartition, hn, domain string, ips []string) error + DeployNetworkingScripts(IDiskPartition, []jsonutils.JSONObject) error + DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error + DeployUdevSubsystemScripts(IDiskPartition) error + DeployFstabScripts(IDiskPartition, []jsonutils.JSONObject) error + GetLoginAccount(IDiskPartition, bool, bool) string + DeployPublicKey(IDiskPartition, string, *sshkeys.SSHKeys) error + ChangeUserPasswd(part IDiskPartition, account, gid, publicKey, password string) (string, error) + DeployYunionroot(IDiskPartition, *sshkeys.SSHKeys) error + EnableSerialConsole(IDiskPartition, *jsonutils.JSONDict) error + DisableSerialConsole(IDiskPartition) error + CommitChanges(IDiskPartition) error + + PrepareFsForTemplate(IDiskPartition) error +} + +type IDebianRootFsDriver interface { + IRootFsDriver + + DistroName() string + VersionFilePath() string +} diff --git a/pkg/hostman/guestfs/fsdriver/linux.go b/pkg/hostman/guestfs/fsdriver/linux.go new file mode 100644 index 0000000000..d890477afe --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/linux.go @@ -0,0 +1,994 @@ +package fsdriver + +import ( + "fmt" + "path" + "regexp" + "strconv" + "strings" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/fstabutils" + "yunion.io/x/onecloud/pkg/util/netutils2" + "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/x/onecloud/pkg/util/sysutils" +) + +const ( + ROOT_USER = "root" + YUNIONROOT_USER = "cloudroot" +) + +type sLinuxRootFs struct { + *sGuestRootFsDriver +} + +func newLinuxRootFs(part IDiskPartition) *sLinuxRootFs { + return &sLinuxRootFs{ + sGuestRootFsDriver: newGuestRootFsDriver(part), + } +} + +func (l *sLinuxRootFs) RootSignatures() []string { + return []string{"/bin", "/etc", "/boot", "/lib", "/usr"} +} + +func (l *sLinuxRootFs) DeployHosts(rootFs IDiskPartition, hostname, domain string, ips []string) error { + var etcHosts = "/etc/hosts" + var oldHostFile string + if rootFs.Exists(etcHosts, false) { + oldhf, err := rootFs.FileGetContents(etcHosts, false) + if err != nil { + return err + } + oldHostFile = string(oldhf) + } + hf := make(fileutils2.HostsFile, 0) + hf.Parse(oldHostFile) + hf.Add("127.0.0.1", "localhost") + for _, ip := range ips { + hf.Add(ip, fmt.Sprintf("%s.%s", hostname, domain), hostname) + } + return rootFs.FilePutContents(etcHosts, hf.String(), false, false) +} + +func (l *sLinuxRootFs) GetLoginAccount(rootFs IDiskPartition, defaultRootUser bool, windowsDefaultAdminUser bool) string { + var selUsr string + if defaultRootUser && rootFs.Exists("/root", false) { + selUsr = ROOT_USER + } else { + usrs := rootFs.ListDir("/home", false) + for _, usr := range usrs { + if usr == YUNIONROOT_USER { + continue + } + if len(selUsr) == 0 || len(selUsr) > len(usr) { + selUsr = usr + } + } + if len(selUsr) == 0 && rootFs.Exists("/root", false) { + selUsr = ROOT_USER + } + } + return selUsr +} + +func (l *sLinuxRootFs) ChangeUserPasswd(rootFs IDiskPartition, account, gid, publicKey, password string) (string, error) { + var secret string + var err error + err = rootFs.Passwd(account, password, false) + if err == nil { + if len(publicKey) > 0 { + secret, err = seclib2.EncryptBase64(publicKey, password) + } else { + secret, err = utils.EncryptAESBase64(gid, password) + } + } else { + return "", fmt.Errorf("ChangeUserPasswd error: %v", err) + } + return secret, err +} + +func (l *sLinuxRootFs) DeployPublicKey(rootFs IDiskPartition, selUsr string, pubkeys *sshkeys.SSHKeys) error { + var usrDir string + if selUsr == "root" { + usrDir = "/root" + } else { + usrDir = path.Join("/home", selUsr) + } + return DeployAuthorizedKeys(rootFs, usrDir, pubkeys, false) +} + +func (l *sLinuxRootFs) DeployYunionroot(rootFs IDiskPartition, pubkeys *sshkeys.SSHKeys) error { + l.DisableSelinux(rootFs) + l.DisableCloudinit(rootFs) + var yunionroot = YUNIONROOT_USER + if err := rootFs.UserAdd(yunionroot, false); err != nil && !strings.Contains(err.Error(), "already exists") { + log.Errorf("UserAdd %s: %v", yunionroot, err) + } + err := DeployAuthorizedKeys(rootFs, path.Join("/home", yunionroot), pubkeys, true) + if err != nil { + return fmt.Errorf("DeployAuthorizedKeys: %v", err) + } + if err := l.EnableUserSudo(rootFs, yunionroot); err != nil { + return fmt.Errorf("EnableUserSudo: %v", err) + } + return nil +} + +func (l *sLinuxRootFs) EnableUserSudo(rootFs IDiskPartition, user string) error { + var sudoDir = "/etc/sudoers.d" + var content = fmt.Sprintf("%s ALL=(ALL) NOPASSWD:ALL\n", user) + if rootFs.Exists(sudoDir, false) { + filepath := path.Join(sudoDir, fmt.Sprintf("90-%s-users", user)) + err := rootFs.FilePutContents(filepath, content, false, false) + if err != nil { + return fmt.Errorf("Write contents to %s: %v", filepath, err) + } + return rootFs.Chmod(filepath, syscall.S_IRUSR|syscall.S_IRGRP, false) + } + return nil +} + +func (l *sLinuxRootFs) DisableSelinux(rootFs IDiskPartition) { + selinuxConfig := "/etc/selinux/config" + content := `# This file controls the state of SELinux on the system. +# SELINUX= can take one of these three values: +# enforcing - SELinux security policy is enforced. +# permissive - SELinux prints warnings instead of enforcing. +# disabled - No SELinux policy is loaded. +SELINUX=disabled +# SELINUXTYPE= can take one of three two values: +# targeted - Targeted processes are protected, +# minimum - Modification of targeted policy. Only selected processes are protected. +# mls - Multi Level Security protection. +SELINUXTYPE=targeted +` + if rootFs.Exists(selinuxConfig, false) { + if err := rootFs.FilePutContents(selinuxConfig, content, false, false); err != nil { + log.Errorf("DisableSelinux error: %v", err) + } + } +} + +func (l *sLinuxRootFs) DisableCloudinit(rootFs IDiskPartition) { + cloudDir := "/etc/cloud" + cloudDisableFile := "/etc/cloud/cloud-init.disabled" + if rootFs.Exists(cloudDir, false) { + if err := rootFs.FilePutContents(cloudDisableFile, "", false, false); err != nil { + log.Errorf("DisableCloudinit error: %v", err) + } + } +} + +func (l *sLinuxRootFs) DeployFstabScripts(rootFs IDiskPartition, disks []jsonutils.JSONObject) error { + fstabcont, err := rootFs.FileGetContents("/etc/fstab", false) + if err != nil { + return err + } + var dataDiskIdx = 0 + var rec string + var modeRwxOwner = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + var fstab = fstabutils.FSTabFile(string(fstabcont)) + + for i := 1; i < len(disks); i++ { + diskId, err := disks[i].GetString("disk_id") + if err != nil { + diskId = "None" + } + dev := fmt.Sprintf("UUID=%s", diskId) + if !fstab.IsExists(dev) { + fs, _ := disks[i].GetString("fs") + if fs == "swap" { + rec = fmt.Sprintf("%s none %s sw 0 0", dev, fs) + } else { + mtPath, _ := disks[i].GetString("mountpoint") + if len(mtPath) == 0 { + mtPath = "/data" + if dataDiskIdx > 0 { + mtPath += fmt.Sprintf("%d", dataDiskIdx) + } + dataDiskIdx += 1 + } + rec = fmt.Sprintf("%s %s %s defaults 2 2", dev, mtPath, fs) + if !l.rootFs.Exists(mtPath, false) { + if err := l.rootFs.Mkdir(mtPath, modeRwxOwner, false); err != nil { + return err + } + } + } + fstab.AddFsrec(rec) + } + } + cf := fstab.ToConf() + return rootFs.FilePutContents("/etc/fstab", cf, false, false) +} + +func (l *sLinuxRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { + udevPath := "/etc/udev/rules.d/" + if rootFs.Exists(udevPath, false) { + rules := rootFs.ListDir(udevPath, false) + for _, rule := range rules { + if strings.Index(rule, "persistent-net.rules") > 0 { + rootFs.Remove(path.Join(udevPath, rule), false) + } else if strings.Index(rule, "persistent-cd.rules") > 0 { + if err := rootFs.FilePutContents(path.Join(udevPath, rule), "", false, false); err != nil { + return err + } + } + } + var nicRules string + for _, nic := range nics { + nicRules += `KERNEL=="eth*", SUBSYSTEM=="net", ACTION=="add", ` + nicRules += `DRIVERS=="?*", ` + mac, _ := nic.GetString("mac") + nicRules += fmt.Sprintf(`ATTR{address}=="%s", ATTR{type}=="1", `, strings.ToLower(mac)) + idx, _ := nic.Int("index") + nicRules += fmt.Sprintf("NAME=\"eth%d\"\n", idx) + } + if err := rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { + return err + } + + var usbRules string + usbRules = `SUBSYSTEM=="usb", ATTRS{idVendor}=="1d6b", ATTRS{idProduct}=="0001", ` + usbRules += "RUN+=" + `"/bin/sh -c \'echo enabled > /sys$env{DEVPATH}/../power/wakeup\'"` + "\n" + if err := rootFs.FilePutContents(path.Join(udevPath, + "90-usb-tablet-remote-wakeup.rules"), usbRules, false, false); err != nil { + return err + } + } + return nil +} + +func (l *sLinuxRootFs) DeployStandbyNetworkingScripts(rootFs IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error { + var udevPath = "/etc/udev/rules.d/" + var nicRules string + for _, nic := range nicsStandby { + nicType, _ := nic.GetString("nic_type") + if !nic.Contains("nic_type") || nicType != "impi" { + nicRules += `KERNEL=="eth*", SUBSYSTEM=="net", ACTION=="add", ` + nicRules += `DRIVERS=="?*", ` + mac, _ := nic.GetString("mac") + nicRules += fmt.Sprintf(`ATTR{address}=="%s", ATTR{type}=="1", `, strings.ToLower(mac)) + idx, _ := nic.Int("index") + nicRules += fmt.Sprintf("NAME=\"eth%d\"\n", idx) + } + } + if err := rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { + return err + } + return nil +} + +func (l *sLinuxRootFs) GetOs() string { + return "Linux" +} + +func (l *sLinuxRootFs) GetArch(rootFs IDiskPartition) string { + if rootFs.Exists("/lib64", false) && rootFs.Exists("/usr/lib64", false) { + return "x86_64" + } else { + return "x86" + } +} + +func (l *sLinuxRootFs) PrepareFsForTemplate(rootFs IDiskPartition) error { + // clean /etc/fstab + if rootFs.Exists("/etc/fstab", false) { + fstabcont, _ := rootFs.FileGetContents("/etc/fstab", false) + fstab := fstabutils.FSTabFile(string(fstabcont)) + fstab.RemoveDevices(1) + cf := fstab.ToConf() + if err := rootFs.FilePutContents("/etc/fstab", cf, false, false); err != nil { + return err + } + } + // rm /etc/ssh/*_key.* + if rootFs.Exists("/etc/ssh", false) { + for _, f := range l.rootFs.ListDir("/etc/ssh", false) { + if strings.HasSuffix(f, "_key") || strings.HasSuffix(f, "_key.pub") { + rootFs.Remove("/etc/ssh/"+f, false) + } + } + } + // clean cloud-init + if rootFs.Exists("/var/lib/cloud", false) { + if err := rootFs.Cleandir("/var/lib/cloud", false, false); err != nil { + return err + } + } + cloudDisableFile := "/etc/cloud/cloud-init.disabled" + if rootFs.Exists(cloudDisableFile, false) { + rootFs.Remove(cloudDisableFile, false) + } + // clean /tmp /var/log /var/cache /var/spool /var/run + for _, dir := range []string{"/tmp", "/var/tmp"} { + if rootFs.Exists(dir, false) { + if err := rootFs.Cleandir(dir, false, false); err != nil { + return err + } + } + } + for _, dir := range []string{"/var/log", "/var/cache", "/usr/local/var/log", "/usr/local/var/cache"} { + if rootFs.Exists(dir, false) { + if err := l.rootFs.Zerofiles(dir, false); err != nil { + return err + } + } + } + for _, dir := range []string{"/var/spool", "/var/run", "/run", "/usr/local/var/spool", "/usr/local/var/run"} { + if rootFs.Exists(dir, false) { + if err := rootFs.Cleandir(dir, true, true); err != nil { + return err + } + } + } + return nil +} + +func (l *sLinuxRootFs) getSerialPorts(rootFs IDiskPartition) []string { + if !rootFs.SupportSerialPorts() { + return nil + } + // XXX HACK, only sshpart.SSHPartition support this + var confpath = "/proc/tty/driver/serial" + content, err := rootFs.FileGetContents(confpath, false) + if err != nil { + log.Errorf("Get %s error: %v", confpath, err) + return nil + } + return sysutils.GetSerialPorts(strings.Split(string(content), "\n")) +} + +func (l *sLinuxRootFs) enableSerialConsoleInitCentos(rootFs IDiskPartition) error { + // http://www.jonno.org/drupal/node/10 + var err error + for _, tty := range l.getSerialPorts(rootFs) { + content := fmt.Sprintf( + `stop on runlevel [016] +start on runlevel [345] +instance %s +respawn +pre-start exec /sbin/securetty %s +exec /sbin/agetty /dev/%s 115200 vt100`, tty, tty, tty) + err = rootFs.FilePutContents(fmt.Sprintf("/etc/init/%s.conf", tty), content, false, false) + } + return err +} + +func (l *sLinuxRootFs) enableSerialConsoleInit(rootFs IDiskPartition) error { + // https://help.ubuntu.com/community/SerialConsoleHowto + var err error + for _, tty := range l.getSerialPorts(rootFs) { + content := fmt.Sprintf( + `start on stopped rc or RUNLEVEL=[12345] +stop on runlevel [!12345] +respawn +exec /sbin/getty -L 115200 %s vt102`, tty) + err = rootFs.FilePutContents(fmt.Sprintf("/etc/init/%s.conf", tty), content, false, false) + } + return err +} + +func (l *sLinuxRootFs) disableSerialConsoleInit(rootFs IDiskPartition) { + for _, tty := range l.getSerialPorts(rootFs) { + path := fmt.Sprintf("/etc/init/%s.conf", tty) + if rootFs.Exists(path, false) { + rootFs.Remove(path, false) + } + } +} + +func (l *sLinuxRootFs) enableSerialConsoleSystemd(rootFs IDiskPartition) error { + for _, tty := range l.getSerialPorts(rootFs) { + sPath := fmt.Sprintf("/etc/systemd/system/getty.target.wants/getty@%s.service", tty) + if rootFs.Exists(sPath, false) { + //rootFs.Symlink("/usr/lib/systemd/system/getty@.service", sPath) + } + } + return nil +} + +func (l *sLinuxRootFs) disableSerialConsoleSystemd(rootFs IDiskPartition) { + for _, tty := range l.getSerialPorts(rootFs) { + sPath := fmt.Sprintf("/etc/systemd/system/getty.target.wants/getty@%s.service", tty) + if rootFs.Exists(sPath, false) { + rootFs.Remove(sPath, false) + } + } +} + +type sDebianLikeRootFs struct { + *sLinuxRootFs +} + +func newDebianLikeRootFs(part IDiskPartition) *sDebianLikeRootFs { + return &sDebianLikeRootFs{ + sLinuxRootFs: newLinuxRootFs(part), + } +} + +func (d *sDebianLikeRootFs) GetReleaseInfo(rootFs IDiskPartition, driver IDebianRootFsDriver) *SReleaseInfo { + version, err := rootFs.FileGetContents(driver.VersionFilePath(), false) + if err != nil { + log.Errorf("Get %s error: %v", driver.VersionFilePath(), err) + return nil + } + versionStr := strings.TrimSpace(string(version)) + return &SReleaseInfo{ + Distro: driver.DistroName(), + Version: versionStr, + Arch: d.GetArch(rootFs), + } +} + +func (d *sDebianLikeRootFs) RootSignatures() []string { + sig := d.sLinuxRootFs.RootSignatures() + return append([]string{"/etc/hostname"}, sig...) +} + +func (d *sDebianLikeRootFs) DeployHostname(rootFs IDiskPartition, hn, domain string) error { + return rootFs.FilePutContents("/etc/hostname", hn, false, false) +} + +func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { + if err := d.sLinuxRootFs.DeployNetworkingScripts(rootFs, nics); err != nil { + return err + } + fn := "/etc/network/interfaces" + cmds := "" + cmds += "auto lo\n" + cmds += "iface lo inet loopback\n\n" + mainNic, err := netutils2.GetMainNic(nics) + if err != nil { + return err + } + var mainIp string + if mainNic != nil { + mainIp, _ = mainNic.GetString("ip") + } + for _, nic := range nics { + var nicDesc = new(types.SServerNic) + err := nic.Unmarshal(nicDesc) + if err != nil { + return err + } + nicIdx, err := nic.Int("index") + if err != nil { + return err + } + cmds += fmt.Sprintf("auto eth%d\n", nicIdx) + if jsonutils.QueryBoolean(nic, "virtual", false) { + cmds += fmt.Sprintf("iface eth%d inet static\n", nicIdx) + cmds += fmt.Sprintf(" address %s\n", netutils2.PSEUDO_VIP) + cmds += " netmask 255.255.255.255\n" + cmds += "\n" + } else if jsonutils.QueryBoolean(nic, "manual", false) { + netmask := netutils2.Netlen2Mask(nicDesc.Masklen) + ip, err := nic.GetString("ip") + if err != nil { + return err + } + cmds += fmt.Sprintf("iface eth%d inet static\n", nicIdx) + cmds += fmt.Sprintf(" address %s\n", ip) + cmds += fmt.Sprintf(" netmask %s\n", netmask) + if len(nicDesc.Gateway) > 0 && ip == mainIp { + cmds += fmt.Sprintf(" gateway %s\n", nicDesc.Gateway) + } + var routes = make([][]string, 0) + netutils2.AddNicRoutes(&routes, nicDesc, mainIp, len(nics), options.HostOptions.PrivatePrefixes) + for _, r := range routes { + cmds += fmt.Sprintf(" up route add -net %s gw %s || true\n", r[0], r[1]) + cmds += fmt.Sprintf(" down route del -net %s gw %s || true\n", r[0], r[1]) + } + dnslist := netutils2.GetNicDns(nicDesc) + if len(dnslist) > 0 { + cmds += fmt.Sprintf(" dns-nameservers %s\n", strings.Join(dnslist, " ")) + cmds += fmt.Sprintf(" dns-search %s\n", nicDesc.Domain) + } + cmds += "\n" + } else { + cmds += fmt.Sprintf("iface eth%d inet dhcp\n\n", nicIdx) + } + } + return rootFs.FilePutContents(fn, cmds, false, false) +} + +type SDebianRootFs struct { + *sDebianLikeRootFs +} + +func NewDebianRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SDebianRootFs) + driver.sDebianLikeRootFs = newDebianLikeRootFs(part) + return driver +} + +func (d *SDebianRootFs) String() string { + return "DebianRootFs" +} + +func (d *SDebianRootFs) GetName() string { + return "Debian" +} + +func (d *SDebianRootFs) DistroName() string { + return d.GetName() +} + +func (d *SDebianRootFs) VersionFilePath() string { + return "/etc/debian_version" +} + +func (d *SDebianRootFs) rootSignatures(driver IDebianRootFsDriver) []string { + sig := d.sDebianLikeRootFs.RootSignatures() + return append([]string{driver.VersionFilePath()}, sig...) +} + +func (d *SDebianRootFs) RootSignatures() []string { + return d.rootSignatures(d) +} + +func (d *SDebianRootFs) RootExcludeSignatures() []string { + return []string{"/etc/lsb-release"} +} + +func (d *SDebianRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + return d.sDebianLikeRootFs.GetReleaseInfo(rootFs, d) +} + +type SCirrosRootFs struct { + *SDebianRootFs +} + +func NewCirrosRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SCirrosRootFs) + driver.SDebianRootFs = NewDebianRootFs(part).(*SDebianRootFs) + return driver +} + +func (d *SCirrosRootFs) GetName() string { + return "Cirros" +} + +func (d *SCirrosRootFs) String() string { + return "CirrosRootFs" +} + +func (d *SCirrosRootFs) DistroName() string { + return d.GetName() +} + +func (d *SCirrosRootFs) VersionFilePath() string { + return "/etc/br-version" +} + +func (d *SCirrosRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + return d.SDebianRootFs.sDebianLikeRootFs.GetReleaseInfo(rootFs, d) +} + +func (d *SCirrosRootFs) RootSignatures() []string { + return d.rootSignatures(d) +} + +type SCirrosNewRootFs struct { + *SDebianRootFs +} + +func NewCirrosNewRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SCirrosNewRootFs) + driver.SDebianRootFs = NewDebianRootFs(part).(*SDebianRootFs) + return driver +} + +func (d *SCirrosNewRootFs) GetName() string { + return "Cirros" +} + +func (d *SCirrosNewRootFs) String() string { + return "CirrosNewRootFs" +} + +func (d *SCirrosNewRootFs) DistroName() string { + return d.GetName() +} + +func (d *SCirrosNewRootFs) VersionFilePath() string { + return "/etc/cirros/version" +} + +func (d *SCirrosNewRootFs) RootSignatures() []string { + return d.rootSignatures(d) +} + +func (d *SCirrosNewRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + return d.SDebianRootFs.sDebianLikeRootFs.GetReleaseInfo(rootFs, d) +} + +type SUbuntuRootFs struct { + *sDebianLikeRootFs +} + +func NewUbuntuRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SUbuntuRootFs) + driver.sDebianLikeRootFs = newDebianLikeRootFs(part) + return driver +} + +func (d *SUbuntuRootFs) RootSignatures() []string { + sig := d.sDebianLikeRootFs.RootSignatures() + return append([]string{"/etc/lsb-release"}, sig...) +} + +func (d *SUbuntuRootFs) GetName() string { + return "Ubuntu" +} + +func (d *SUbuntuRootFs) String() string { + return "UbuntuRootFs" +} + +func (d *SUbuntuRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + distroKey := "DISTRIB_RELEASE=" + rel, err := rootFs.FileGetContents("/etc/lsb-release", false) + if err != nil { + log.Errorf("Get ubuntu release info error: %v", err) + return nil + } + var version string + lines := strings.Split(string(rel), "\n") + for _, l := range lines { + if strings.HasPrefix(l, distroKey) { + version = strings.TrimSpace(l[len(distroKey) : len(l)-1]) + } + } + return newReleaseInfo(d.GetName(), version, d.GetArch(rootFs)) +} + +func (d *SUbuntuRootFs) EnableSerialConsole(rootFs IDiskPartition, sysInfo *jsonutils.JSONDict) error { + relInfo := d.GetReleaseInfo(rootFs) + ver := strings.Split(relInfo.Version, ".") + verInt, _ := strconv.Atoi(ver[0]) + if verInt < 16 { + return d.enableSerialConsoleInit(rootFs) + } + return d.enableSerialConsoleSystemd(rootFs) +} + +func (d *SUbuntuRootFs) DisableSerialConcole(rootFs IDiskPartition) error { + relInfo := d.GetReleaseInfo(rootFs) + ver := strings.Split(relInfo.Version, ".") + verInt, _ := strconv.Atoi(ver[0]) + if verInt < 16 { + d.disableSerialConsoleInit(rootFs) + return nil + } + d.disableSerialConsoleSystemd(rootFs) + return nil +} + +type sRedhatLikeRootFs struct { + *sLinuxRootFs +} + +func newRedhatLikeRootFs(part IDiskPartition) *sRedhatLikeRootFs { + return &sRedhatLikeRootFs{ + sLinuxRootFs: newLinuxRootFs(part), + } +} + +func (r *sRedhatLikeRootFs) RootSignatures() []string { + sig := r.sLinuxRootFs.RootSignatures() + return append([]string{"/etc/sysconfig/network", "/etc/redhat-release"}, sig...) +} + +func (r *sRedhatLikeRootFs) DeployHostname(rootFs IDiskPartition, hn, domain string) error { + var sPath = "/etc/sysconfig/network" + centosHn := "" + centosHn += "NETWORKING=yes\n" + centosHn += fmt.Sprintf("HOSTNAME=%s.%s\n", hn, domain) + if err := rootFs.FilePutContents(sPath, centosHn, false, false); err != nil { + return err + } + if rootFs.Exists("/etc/hostname", false) { + return rootFs.FilePutContents("/etc/hostname", hn, false, false) + } + return nil +} + +/* + udev_path = '/etc/udev/rules.d/' + if self.root_fs.exists(udev_path): + nic_rules = '' + for nic in nics: + nic_rules += 'KERNEL=="eth*", ' + nic_rules += 'SYSFS{address}=="%s", ' % (nic['mac'].lower()) + nic_rules += 'NAME="eth%d"\n' % (nic['index']) + print nic_rules + self.root_fs.file_put_contents(os.path.join(udev_path, '60-net.rules'), nic_rules) +*/ + +func (r *sRedhatLikeRootFs) Centos5DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { + var udevPath = "/etc/udev/rules.d/" + if rootFs.Exists(udevPath, false) { + var nicRules = "" + for _, nic := range nics { + var nicdesc = new(types.SServerNic) + if err := nic.Unmarshal(nicdesc); err != nil { + return err + } + nicRules += `KERNEL=="eth*", ` + nicRules += fmt.Sprintf(`SYSFS{address}=="%s", `, strings.ToLower(nicdesc.Mac)) + nicRules += fmt.Sprintf("NAME=\"eth%d\"\n", nicdesc.Index) + } + return rootFs.FilePutContents(path.Join(udevPath, "60-net.rules"), + nicRules, false, false) + } + return nil +} + +func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject, relInfo *SReleaseInfo) error { + ver := strings.Split(relInfo.Version, ".") + iv, err := strconv.ParseInt(ver[0], 10, 0) + if err != nil { + return fmt.Errorf("Failed to get release version: %v", err) + } + if iv < 6 { + err = r.Centos5DeployNetworkingScripts(rootFs, nics) + } else { + err = r.sLinuxRootFs.DeployNetworkingScripts(rootFs, nics) + } + if err != nil { + return err + } + mainNic, err := netutils2.GetMainNic(nics) + if err != nil { + return err + } + var mainIp string + if mainNic != nil { + mainIp, _ = mainNic.GetString("ip") + } + for _, nic := range nics { + var cmds string + var nicdesc = new(types.SServerNic) + if err := nic.Unmarshal(nicdesc); err != nil { + return err + } + cmds += fmt.Sprintf("DEVICE=eth%d\n", nicdesc.Index) + cmds += fmt.Sprintf("NAME=eth%d\n", nicdesc.Index) + cmds += fmt.Sprintf("HWADDR=%s\n", nicdesc.Mac) + cmds += fmt.Sprintf("MACADDR=%s\n", nicdesc.Mac) + if nicdesc.Virtual { + cmds += "BOOTPROTO=none\n" + cmds += "NETMASK=255.255.255.255\n" + cmds += fmt.Sprintf("IPADDR=%s\n", netutils2.PSEUDO_VIP) + cmds += "USERCTL=no\n" + } else if nicdesc.Manual { + netmask := netutils2.Netlen2Mask(nicdesc.Masklen) + cmds += "BOOTPROTO=none\n" + cmds += fmt.Sprintf("NETMASK=%s\n", netmask) + cmds += fmt.Sprintf("IPADDR=%s\n", nicdesc.Ip) + cmds += "USERCTL=no\n" + if len(nicdesc.Gateway) > 0 && nicdesc.Ip == mainIp { + cmds += fmt.Sprintf("GATEWAY=%s\n", nicdesc.Gateway) + } + var routes = make([][]string, 0) + var rtbl string + netutils2.AddNicRoutes(&routes, nicdesc, mainIp, len(nics), options.HostOptions.PrivatePrefixes) + for _, r := range routes { + rtbl += fmt.Sprintf("%s via %s dev eth%d\n", r[0], r[1], nicdesc.Index) + } + if len(rtbl) > 0 { + var fn = fmt.Sprintf("/etc/sysconfig/network-scripts/route-eth%d", nicdesc.Index) + if err := rootFs.FilePutContents(fn, rtbl, false, false); err != nil { + return err + } + } + dnslist := netutils2.GetNicDns(nicdesc) + if len(dnslist) > 0 { + cmds += "PEERDNS=yes\n" + for i := 0; i < len(dnslist); i++ { + cmds += fmt.Sprintf("DNS%d=%s\n", i+1, dnslist[i]) + } + cmds += fmt.Sprintf("DOMAIN=%s\n", nicdesc.Domain) + } + } else { + cmds += "BOOTPROTO=dhcp\n" + } + var fn = fmt.Sprintf("/etc/sysconfig/network-scripts/ifcfg-eth%d", nicdesc.Index) + if err := rootFs.FilePutContents(fn, cmds, false, false); err != nil { + return err + } + } + return nil +} + +func (r *sRedhatLikeRootFs) DeployStandbyNetworkingScripts(rootFs IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error { + if err := r.sLinuxRootFs.DeployStandbyNetworkingScripts(rootFs, nics, nicsStandby); err != nil { + return err + } + for _, nic := range nicsStandby { + var cmds string + var nicdesc = new(types.SServerNic) + if err := nic.Unmarshal(nicdesc); err != nil { + return err + } + if nicType, err := nic.GetString("nic_type"); err != nil && nicType != "ipmi" { + cmds += fmt.Sprintf("DEVICE=eth%d\n", nicdesc.Index) + cmds += fmt.Sprintf("NAME=eth%d\n", nicdesc.Index) + cmds += fmt.Sprintf("HWADDR=%s\n", nicdesc.Mac) + cmds += fmt.Sprintf("MACADDR=%s\n", nicdesc.Mac) + cmds += "ONBOOT=no\n" + var fn = fmt.Sprintf("/etc/sysconfig/network-scripts/ifcfg-eth%d", nicdesc.Index) + if err := rootFs.FilePutContents(fn, cmds, false, false); err != nil { + return err + } + } + } + return nil +} + +//TODO enable_serial_console +//TODO disable_serial_console + +type SCentosRootFs struct { + *sRedhatLikeRootFs +} + +func NewCentosRootFs(part IDiskPartition) IRootFsDriver { + return &SCentosRootFs{sRedhatLikeRootFs: newRedhatLikeRootFs(part)} +} + +func (c *SCentosRootFs) String() string { + return "CentosRootFs" +} + +func (c *SCentosRootFs) GetName() string { + return "CentOS" +} + +func (c *SCentosRootFs) RootSignatures() []string { + sig := c.sRedhatLikeRootFs.RootSignatures() + return append([]string{"/etc/centos-release"}, sig...) +} + +func (c *SCentosRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + rel, _ := rootFs.FileGetContents("/etc/centos-release", false) + var version string + if len(rel) > 0 { + re := regexp.MustCompile(`^\d+\.\d+`) + dat := strings.Split(string(rel), " ") + for _, v := range dat { + if re.Match([]byte(v)) { + version = v + break + } + } + } + return newReleaseInfo(c.GetName(), version, c.GetArch(rootFs)) +} + +func (c *SCentosRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { + relInfo := c.GetReleaseInfo(rootFs) + if err := c.sRedhatLikeRootFs.deployNetworkingScripts(rootFs, nics, relInfo); err != nil { + return err + } + var udevPath = "/etc/udev/rules.d/" + var files = []string{"60-net.rules", "75-persistent-net-generator.rules"} + for _, f := range files { + sPath := path.Join(udevPath, f) + if !rootFs.Exists(sPath, false) { + if err := rootFs.FilePutContents(sPath, "", false, false); err != nil { + return err + } + } + } + return nil +} + +type SFedoraRootFs struct { + *sRedhatLikeRootFs +} + +func NewFedoraRootFs(part IDiskPartition) IRootFsDriver { + return &SFedoraRootFs{sRedhatLikeRootFs: newRedhatLikeRootFs(part)} +} + +func (c *SFedoraRootFs) String() string { + return "FedoraRootFs" +} + +func (c *SFedoraRootFs) GetName() string { + return "Fedora" +} + +func (c *SFedoraRootFs) RootSignatures() []string { + sig := c.sRedhatLikeRootFs.RootSignatures() + return append([]string{"/etc/fedora-release"}, sig...) +} + +func (c *SFedoraRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + rel, _ := rootFs.FileGetContents("/etc/fedora-release", false) + var version string + if len(rel) > 0 { + re := regexp.MustCompile(`^\d+`) + dat := strings.Split(string(rel), " ") + for _, v := range dat { + if re.Match([]byte(v)) { + version = v + break + } + } + } + return newReleaseInfo(c.GetName(), version, c.GetArch(rootFs)) +} + +type SRhelRootFs struct { + *sRedhatLikeRootFs +} + +func NewRhelRootFs(part IDiskPartition) IRootFsDriver { + return &SRhelRootFs{sRedhatLikeRootFs: newRedhatLikeRootFs(part)} +} + +func (d *SRhelRootFs) GetName() string { + return "RHEL" +} + +func (d *SRhelRootFs) String() string { + return "RhelRootFs" +} + +func (d *SRhelRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + rel, _ := rootFs.FileGetContents("/etc/redhat-release", false) + var version string + if len(rel) > 0 { + dat := strings.Split(string(rel), " ") + if len(dat) > 6 { + version = dat[6] + } + } + return newReleaseInfo(d.GetName(), version, d.GetArch(rootFs)) +} + +/*type SGentooRootFs struct { + *sLinuxRootFs +} + +func NewGentooRootFs(part IDiskPartition) IRootFsDriver { + return &SGentooRootFs{sLinuxRootFs: newLinuxRootFs(part)} +} + +type SArchLinuxRootFs struct { + *sLinuxRootFs +} + +func NewArchLinuxRootFs(part IDiskPartition) IRootFsDriver { + return &SArchLinuxRootFs{sLinuxRootFs: newLinuxRootFs(part)} +} + +type SOpenWrtRootFs struct { + *sLinuxRootFs +} + +func NewOpenWrtRootFs(part IDiskPartition) IRootFsDriver { + return &SOpenWrtRootFs{sLinuxRootFs: newLinuxRootFs(part)} +} + +type SCoreOsRootFs struct { + *SGuestRootFsDriver +} + +func NewCoreOsRootFs(part IDiskPartition) IRootFsDriver { + return &SCoreOsRootFs{SGuestRootFsDriver: newGuestRootFsDriver(part)} +}*/ diff --git a/pkg/hostman/guestfs/fsdriver/macos.go b/pkg/hostman/guestfs/fsdriver/macos.go new file mode 100644 index 0000000000..e1ebaab2b2 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/macos.go @@ -0,0 +1,9 @@ +package fsdriver + +type SMacOSRootFs struct { + *sGuestRootFsDriver +} + +//func NewMacOSRootFs(part IDiskPartition) IRootFsDriver { +//return &SMacOSRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/hostman/guestfs/fsdriver/windows.go b/pkg/hostman/guestfs/fsdriver/windows.go new file mode 100644 index 0000000000..55e3275f18 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/windows.go @@ -0,0 +1,428 @@ +package fsdriver + +import ( + "fmt" + "math/rand" + "path" + "regexp" + "strings" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/netutils2" + "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/x/onecloud/pkg/util/stringutils2" + "yunion.io/x/onecloud/pkg/util/version" + "yunion.io/x/onecloud/pkg/util/winutils" + "yunion.io/x/pkg/utils" +) + +const ( + TCPIP_PARAM_KEY = `HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters` + BOOT_SCRIPT_PATH = "/Windows/System32/GroupPolicy/Machine/Scripts/Startup/cloudboot.bat" + WIN_BOOT_SCRIPT_PATH = "cloudboot.bat" +) + +type SWindowsRootFs struct { + *sGuestRootFsDriver + + guestDebugLogPath string + bootScripts string +} + +func NewWindowsRootFs(part IDiskPartition) IRootFsDriver { + seq := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} + suffix := make([]byte, 16) + lenSeq := len(seq) + for i := 0; i < 16; i++ { + suffix[i] = seq[rand.Intn(lenSeq)] + } + return &SWindowsRootFs{ + sGuestRootFsDriver: newGuestRootFsDriver(part), + guestDebugLogPath: `%SystemRoot%\mdbg_` + string(suffix), + } +} + +func (w *SWindowsRootFs) IsFsCaseInsensitive() bool { + return true +} + +func (w *SWindowsRootFs) GetName() string { + return "Windows" +} + +func (w *SWindowsRootFs) String() string { + return "WindowsRootFs" +} + +func (w *SWindowsRootFs) DeployPublicKey(IDiskPartition, string, *sshkeys.SSHKeys) error { + return nil +} + +func (w *SWindowsRootFs) RootSignatures() []string { + return []string{ + "/program files", "/windows", + "/windows/system32/drivers/etc", "/windows/system32/config", + "/windows/system32/config/sam", + "/windows/system32/config/software", + "/windows/system32/config/system", + } +} + +func (w *SWindowsRootFs) GetReleaseInfo(IDiskPartition) *SReleaseInfo { + confPath := w.rootFs.GetLocalPath("/windows/system32/config", true) + tool := winutils.NewWinRegTool(confPath) + if tool.CheckPath() { + distro := tool.GetProductName() + version := tool.GetVersion() + arch := tool.GetArch() + lan := tool.GetInstallLanguage() + return &SReleaseInfo{distro, version, arch, lan} + } else { + return nil + } +} + +func (w *SWindowsRootFs) GetLoginAccount(rootFs IDiskPartition, defaultRootUser bool, windowsDefaultAdminUser bool) string { + confPath := w.rootFs.GetLocalPath("/windows/system32/config", true) + tool := winutils.NewWinRegTool(confPath) + tool.CheckPath() + users := tool.GetUsers() + admin := "Administrator" + selUsr := "" + if w.IsWindows10() { + delete(users, admin) + } + if _, ok := users[admin]; ok && windowsDefaultAdminUser { + selUsr = admin + } else { + for user := range users { + if user != admin && (len(selUsr) == 0 || len(selUsr) > len(user)) { + selUsr = user + } + } + if _, ok := users[admin]; ok && len(selUsr) == 0 { + selUsr = admin + } + } + if len(selUsr) > 0 { + if _, ok := users[selUsr]; !ok { + tool.UnlockUser(selUsr) + } + } + return selUsr +} + +func (w *SWindowsRootFs) IsWindows10() bool { + info := w.GetReleaseInfo(nil) + if info != nil && strings.HasPrefix(info.Distro, "Windows 10 ") { + return true + } + return false +} + +func (w *SWindowsRootFs) GetOs() string { + return "Windows" +} + +func (w *SWindowsRootFs) appendGuestBootScript(content string) string { + w.bootScripts += "\r\n" + content + return w.bootScripts +} + +func (w *SWindowsRootFs) regAdd(path, key, val, regType string) string { + return fmt.Sprintf(`REG ADD %s /V "%s" /D "%s" /T %s /F`, path, key, val, regType) +} + +func (w *SWindowsRootFs) putGuestScriptContents(spath, content string) error { + contentArr := []string{} + contentLen := len(content) + + var j = 0 + for i := 1; i < contentLen; i++ { + if content[i] == '\n' && content[i-1] != '\r' { + contentArr = append(contentArr, content[j:i]) + j = i + 1 + } + } + if j < contentLen { + contentArr = append(contentArr, content[j:]) + } + + content = strings.Join(contentArr, "\r\n") + return w.rootFs.FilePutContents(spath, content, false, true) +} + +func (w *SWindowsRootFs) DeployHostname(part IDiskPartition, hostname, domain string) error { + bootScript := strings.Join([]string{ + `set HOSTNAME_SCRIPT=%SystemRoot%\hostnamecfg.bat`, + `if exist %HOSTNAME_SCRIPT% (`, + ` call %HOSTNAME_SCRIPT%`, + ` del %HOSTNAME_SCRIPT%`, + `)`, + }, "\r\n") + w.appendGuestBootScript(bootScript) + + lines := []string{} + for k, v := range map[string]string{ + "Hostname": hostname, + "Domain": domain, + "NV Hostname": hostname, + "NV Domain": domain, + } { + lines = append(lines, w.regAdd(TCPIP_PARAM_KEY, k, v, "REG_SZ")) + } + hostScripts := strings.Join(lines, "\r\n") + return w.putGuestScriptContents("/windows/hostnamecfg.bat", hostScripts) +} + +func (w *SWindowsRootFs) DeployHosts(part IDiskPartition, hn, domain string, ips []string) error { + var ( + ETC_HOSTS = "/windows/system32/drivers/etc/hosts" + oldHf = "" + ) + + if w.rootFs.Exists(ETC_HOSTS, true) { + oldHfBytes, err := w.rootFs.FileGetContents(ETC_HOSTS, true) + if err != nil { + log.Errorln(err) + return err + } + oldHf = string(oldHfBytes) + } + + hf := fileutils2.HostsFile{} + hf.Parse(oldHf) + hf.Add("127.0.0.1", "localhost") + for _, ip := range ips { + hf.Add(ip, fmt.Sprintf("%s.%s", hn, domain), hn) + } + return w.rootFs.FilePutContents(ETC_HOSTS, hf.String(), false, true) +} + +func (w *SWindowsRootFs) DeployNetworkingScripts(rootfs IDiskPartition, nics []jsonutils.JSONObject) error { + mainNic, err := netutils2.GetMainNic(nics) + if err != nil { + return err + } + mainIp := "" + if mainNic != nil { + mainIp, _ = mainNic.GetString("ip") + } + bootScript := strings.Join([]string{ + `set NETCFG_SCRIPT=%SystemRoot%\netcfg.bat`, + `if exist %NETCFG_SCRIPT% (`, + ` call %NETCFG_SCRIPT%`, + ` del %NETCFG_SCRIPT%`, + `)`, + }, "\r\n") + w.appendGuestBootScript(bootScript) + lines := []string{ + "@echo off", + w.MakeGuestDebugCmd("netcfg step 1"), + "setlocal enableDelayedExpansion", + `for /f "delims=" %%a in (\'getmac /fo csv /nh /v\') do (`, + ` set line=%%a&set line=!line:"=,!`, + ` for /f "delims=,,, tokens=1,3" %%b in ("!line!") do (`, + } + + for _, nic := range nics { + snic := &types.SServerNic{} + if err := nic.Unmarshal(snic); err != nil { + log.Errorln(err) + return err + } + + mac := snic.Mac + mac = strings.Replace(strings.ToUpper(mac), ":", "-", -1) + lines = append(lines, fmt.Sprintf(` if "%%%%c" == "%s" (`, mac)) + if jsonutils.QueryBoolean(nic, "manual", false) { + netmask := netutils2.Netlen2Mask(snic.Masklen) + cfg := fmt.Sprintf(` netsh interface ip set address "%%%%b" static %s %s`, snic.Ip, netmask) + if len(snic.Gateway) > 0 && snic.Ip == mainIp { + cfg += fmt.Sprintf(" %s", snic.Gateway) + } + lines = append(lines, cfg) + routes := [][]string{} + netutils2.AddNicRoutes(&routes, snic, mainIp, len(nics), options.HostOptions.PrivatePrefixes) + for _, r := range routes { + lines = append(lines, fmt.Sprintf(` netsh interface ip add route %s "%%%%b" %s`, r[0], r[1])) + } + dnslist := netutils2.GetNicDns(snic) + if len(dnslist) > 0 { + lines = append(lines, fmt.Sprintf( + ` netsh interface ip set dns name="%%%%b" source=static addr=%s ddns=disabled suffix=interface`, dnslist[0])) + if len(dnslist) > 1 { + for i := 1; i < len(dnslist); i++ { + lines = append(lines, fmt.Sprintf(` netsh interface ip add dns "%%%%b" %s index=%d`, dnslist[i], i+1)) + } + } + } + + if len(snic.Domain) > 0 && snic.Ip == mainIp { + lines = append(lines, w.regAdd(TCPIP_PARAM_KEY, "SearchList", snic.Domain, "REG_SZ")) + } + } else { + lines = append(lines, ` netsh interface ip set address "%%b" dhcp`) + lines = append(lines, ` netsh interface ip set dns "%%b" dhcp`) + } + lines = append(lines, ` )`) + } + lines = append(lines, ` )`) + lines = append(lines, `)`) + lines = append(lines, w.MakeGuestDebugCmd("netcfg step 2")) + lines = append(lines, `netsh advfirewall firewall set rule group=\"remote desktop\" new enable=yes`) + netScript := strings.Join(lines, "\r\n") + return w.putGuestScriptContents("/windows/netcfg.bat", netScript) +} + +func (w *SWindowsRootFs) MakeGuestDebugCmd(content string) string { + mark := "=============" + content = regexp.MustCompile(`(["^&<>|])`).ReplaceAllString(content, "^$1") + return fmt.Sprintf("echo %s %s %s >> %s", mark, content, mark, w.guestDebugLogPath) +} + +func (w *SWindowsRootFs) prependGuestBootScript(content string) { + w.bootScripts = content + "\r\n" + w.bootScripts +} + +func (w *SWindowsRootFs) PrepareFsForTemplate(IDiskPartition) error { + for _, f := range []string{"/Pagefile.sys", "/Hiberfil.sys", "/Swapfile.sys"} { + if w.rootFs.Exists(f, true) { + w.rootFs.Remove(f, true) + } + } + return nil +} + +func (w *SWindowsRootFs) CommitChanges(part IDiskPartition) error { + confPath := part.GetLocalPath("/windows/system32/config", true) + tool := winutils.NewWinRegTool(confPath) + tool.CheckPath() + tool.EnableRdp() + tool.InstallGpeditStartScript(WIN_BOOT_SCRIPT_PATH) + if err := w.rootFs.Mkdir(path.Dir(BOOT_SCRIPT_PATH), syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR, true); err != nil { + return err + } + return w.rootFs.FilePutContents(BOOT_SCRIPT_PATH, w.bootScripts, false, false) +} + +func (w *SWindowsRootFs) ChangeUserPasswd(part IDiskPartition, account, gid, publicKey, password string) (string, error) { + rinfo := w.GetReleaseInfo(part) + confPath := part.GetLocalPath("/windows/system32/config", true) + tool := winutils.NewWinRegTool(confPath) + tool.CheckPath() + success := false + if rinfo != nil && version.GE(rinfo.Version, "6.1") { + success = w.deployPublicKeyByGuest(account, password) + } else { + success = tool.ChangePassword(account, password) == nil + } + + var ( + secret string + err error + ) + if success { + if len(publicKey) > 0 { + secret, err = seclib2.EncryptBase64(publicKey, password) + if err != nil { + return "", err + } + } else { + secret, err = utils.EncryptAESBase64(gid, password) + if err != nil { + return "", err + } + } + if rinfo != nil && strings.Contains(rinfo.Distro, "Windows XP") { + if len(tool.GetLogontype()) > 0 { + tool.SetLogontype("0x0") + } + } + } else { + log.Errorf("Filaed Password %s", account) + } + defUanme := tool.GetDefaultAccount() + if len(defUanme) > 0 && defUanme != account { + tool.SetDefaultAccount(account) + } + return secret, nil +} + +func (w *SWindowsRootFs) deployPublicKeyByGuest(uname, passwd string) bool { + if !w.deploySetupCompleteScripts(uname, passwd) { + return false + } + bootScript := strings.Join([]string{ + `set CHANGE_PASSWD_SCRIPT=%SystemRoot%\chgpwd.bat`, + `if exist %CHANGE_PASSWD_SCRIPT% (`, + ` call %CHANGE_PASSWD_SCRIPT%`, + ` del %CHANGE_PASSWD_SCRIPT%`, + `)`, + }, "\r\n") + w.prependGuestBootScript(bootScript) + logPath := w.guestDebugLogPath + chksum := stringutils2.GetMD5Hash(passwd + logPath[(len(logPath)-10):]) + + chgpwdScript := strings.Join([]string{ + w.MakeGuestDebugCmd("change password step 1"), + strings.Join([]string{ + `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe`, + ` -executionpolicy bypass %SystemRoot%\chgpwd.ps1`, + fmt.Sprintf(" %s %s %s %s", uname, passwd, chksum, logPath), + }, ""), + `del %SystemRoot%\chgpwd.ps1`, + w.MakeGuestDebugCmd("change password step 2"), + }, "\r\n") + if w.putGuestScriptContents("/windows/chgpwd.bat", chgpwdScript) != nil { + return false + } + if w.putGuestScriptContents("/windows/chgpwd.ps1", WinScriptChangePassword) != nil { + return false + } + return true +} + +func (w *SWindowsRootFs) deploySetupCompleteScripts(uname, passwd string) bool { + SETUP_SCRIPT_PATH := "/Windows/Setup/Scripts/SetupComplete.cmd" + if !w.rootFs.Exists(path.Dir(SETUP_SCRIPT_PATH), true) { + w.rootFs.Mkdir(path.Dir(SETUP_SCRIPT_PATH), + syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR, true) + } + if w.putGuestScriptContents("/windows/chgpwd_setup.ps1", WinScriptChangePassword) != nil { + return false + } + cmds := []string{ + `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass %SystemRoot%\chgpwd_setup.ps1 '` + + fmt.Sprintf("%s %s", uname, passwd), + "Net stop wuauserv", + } + for _, v := range [][3]string{ + {"AUOptions", "REG_DWORD", "3"}, + {"NoAutoUpdate", "REG_DWORD", "0"}, + {"ScheduledInstallDay", "REG_DWORD", "0"}, + {"ScheduledInstallTime", "REG_DWORD", "4"}, + {"AutoInstallMinorUpdates", "REG_DWORD", "1"}, + {"NoAutoRebootWithLoggedOnUsers", "REG_DWORD", "1"}, + {"IncludeRecommendedUpdates", "REG_DWORD", "0"}, + {"EnableFeaturedSoftware", "REG_DWORD", "1"}, + } { + cmds = append(cmds, fmt.Sprintf(`REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v %s /t %s /d %s /f`, + v[0], v[1], v[2])) + } + cmds = append(cmds, "Net start wuauserv") + cmds = append(cmds, "wuauclt /detectnow") + cmds = append(cmds, `del %SystemRoot%\chgpwd_setup.ps1`) + cmds = append(cmds, `del %SystemRoot%\Setup\Scripts\SetupComplete.cmd`) + if w.putGuestScriptContents(SETUP_SCRIPT_PATH, strings.Join(cmds, "\r\n")) != nil { + return false + } + return true +} diff --git a/pkg/hostman/guestfs/fsdriver/winscripts.go b/pkg/hostman/guestfs/fsdriver/winscripts.go new file mode 100644 index 0000000000..6bd8fce075 --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/winscripts.go @@ -0,0 +1,488 @@ +package fsdriver + +const WinScriptChangePassword = ` + +$username = $args[0] +$passwd = $args[1] +$loghash = $args[2] +$logpath = $args[3] +Function ChangePassword($u, $p) { + $admin = [adsi]("WinNT://./$($u), user") + $succ = 0 + $tried = 0 + $max_tries = 10 + while (($succ -eq 0) -and ($tried -lt $max_tries)) { + Try { + $admin.psbase.invoke("SetPassword", $p) + $admin.psbase.CommitChanges() + $succ = 1 + } Catch { + Start-Sleep -s 1 + } + $tried = $tried + 1 + } +} +if ($username -and $passwd) { + if ($logpath) { + "starting $loghash" | Out-File $logpath -Append -Encoding Default + ChangePassword $username $passwd 2>&1 | Out-File $logpath -Append -Encoding Default + } else { + ChangePassword $username $passwd 2>&1 | Out-Null + } +} + +` + +const WinScriptMountDisk = ` + +var MTW_GLOBAL_FSO = new ActiveXObject('Scripting.FileSystemObject'); +var MTW_SCRIPT_PATH = mtw_gen_script_path(); +var MTW_DEBUG_STREAM = null; + +function mtw_create_shell() { + return new ActiveXObject('WScript.Shell'); +} + +function mtw_gen_script_path() { + var fso = MTW_GLOBAL_FSO; + var folder = fso.GetSpecialFolder(2); // TemporaryFolder + var folder_path = folder + ''; + var script_name = []; + if (!/[\\\/]$/.test(folder_path)) { + folder_path += '\\'; + } + for (var i = 0; i < 5; i++) { + script_name.push(mtw_gen_random_str(6)); + } + return folder_path + script_name.join('-'); +} + +function mtw_gen_random_str(length) { + var offset, result = []; + var charcode_base = 'A'.charCodeAt(0); + for (var i = 0; i < length; i++) { + offset = Math.floor(Math.random() * 26); + result.push(String.fromCharCode(charcode_base + offset)); + } + return result.join(''); +} + +function mtw_prepare_debug(file_path) { + var fso = new ActiveXObject('Scripting.FileSystemObject'); + var stream = fso.OpenTextFile(file_path, 8, true); // 8: ForAppending + + stream.WriteLine(''); + stream.WriteLine('================ ' + new Date() + ' ================'); + stream.WriteLine(''); + + MTW_DEBUG_STREAM = stream; +} + +function mtw_append_debug(cmd_lines, result_lines) { + var i, len, stream = MTW_DEBUG_STREAM; + + if (!stream) return; + + stream.WriteLine(''); + stream.WriteLine('---------- script:'); + for (i = 0, len = cmd_lines.length; i < len; i++) { + stream.WriteLine(cmd_lines[i]); + } + + stream.WriteLine('---------- result:'); + for (i = 0, len = result_lines.length; i < len; i++) { + stream.WriteLine(result_lines[i]); + } +} + +function mtw_execute_diskpart(cmd_lines) { + var result_lines = []; + var fso = MTW_GLOBAL_FSO; + var stream, shell, exec_cmd; + + cmd_lines.push('exit'); + stream = fso.CreateTextFile(MTW_SCRIPT_PATH, true); + for (var i = 0, len = cmd_lines.length; i < len; i++) { + stream.WriteLine(cmd_lines[i]); + } + stream.close(); + + shell = mtw_create_shell(); + exec_cmd = shell.Exec('diskpart /s ' + MTW_SCRIPT_PATH); + while (exec_cmd.Status == 0) { + WScript.Sleep(100); + } + stream = exec_cmd.StdOut; + while (!stream.AtEndOfStream) { + result_lines.push(stream.ReadLine()); + } + fso.DeleteFile(MTW_SCRIPT_PATH); + + mtw_append_debug(cmd_lines, result_lines); + + return result_lines; +} + +function mtw_get_disk_list() { + var result_lines, line, match, disk_no; + var disk_list = []; + + result_lines = mtw_execute_diskpart(['list disk']); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Disk ### Status Size Free Dyn Gpt + -------- ------------- ------- ------- --- --- + Disk 0 Online 20 GB 0 B + Disk 1 Offline 10 GB 0 B + */ + match = line.match(/\s([1-9])\s\D+\s[1-9]\d*\s+[GMK]B\s+\d+\s+[GMK]?B/); + if (match) { + disk_no = match[1]; + disk_list.push({ + 'disk_no': disk_no, + 'partition_list': mtw_get_partition_list(disk_no) + }); + } + } + + return disk_list; +} + +function mtw_get_partition_list(disk_no) { + var result_lines, line, match, partition_no; + var partition_list = []; + + result_lines = mtw_execute_diskpart([ + 'select disk=' + disk_no, + 'list partition' + ]); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Partition ### Type Size Offset + ------------- ---------------- ------- ------- + Partition 1 Primary 9 GB 1024 KB + */ + match = line.match(/\s(\d)\s\D+\s[1-9]\d*\s+[GMK]B\s+\d+\s+[GMK]?B/); + if (match) { + partition_no = match[1]; + partition_list.push({ + 'partition_no': partition_no, + 'partition_type': mtw_get_partition_type(disk_no, partition_no) + }); + } + } + + return partition_list; +} + +var MTW_PARTITION_TYPE_NODATA = 'nodata'; +var MTW_PARTITION_TYPE_INVALID = 'invalid'; +function mtw_get_partition_type(disk_no, partition_no) { + var result_lines, line, match; + var possible_type_list = []; + + result_lines = mtw_execute_diskpart([ + 'select disk=' + disk_no, + 'select partition=' + partition_no, + 'detail partition' + ]); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Partition 1 + Type : 06 + Hidden: No + Active: No + Offset in Bytes: 1048576 + + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + * Volume 3 RAW Partition 9 GB Healthy + */ + match = line.match(/:\s*([0-9a-f]{2})\b/i); + if (match) { + possible_type_list.push(match[1]); + } + } + + switch (possible_type_list.length) { + case 0: + return MTW_PARTITION_TYPE_NODATA; + case 1: + return possible_type_list[0]; + default: + break; + } + return MTW_PARTITION_TYPE_INVALID; +} + +function mtw_get_volume_list(disk_no) { + var result_lines, line, match; + var sep_line_exist = false; + var volume_list = []; + + result_lines = mtw_execute_diskpart([ + 'select disk=' + disk_no, + 'detail disk' + ]); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Red Hat VirtIO SCSI Disk Device + Disk ID: 0004B605 + Type : SCSI + Status : Online + Path : 0 + Target : 0 + LUN ID : 0 + Location Path : PCIROOT(0)#PCI(0600)#SCSI(P00T00L00) + Current Read-only State : No + Read-only : No + Boot Disk : No + Pagefile Disk : No + Hibernation File Disk : No + Crashdump Disk : No + Clustered Disk : No + + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + Volume 3 RAW Partition 9 GB Healthy + */ + if (!sep_line_exist) { + match = line.match(/-+\s+-+\s+-+\s+-+/); + if (match) { + sep_line_exist = true; + } + } else { + match = line.match(/\s(\d)\s.+\s\d+\s+[GMK]?B/); + if (match) { + volume_list.push({'volume_no': match[1]}); + } + } + } + + return volume_list; +} + +function mtw_assign_volume_letter(volume_no_set, letter_offset) { + var result_lines, line, match, i, len; + var volume_no, letter, charcode, charcode_max, letter_set; + var volume_map = {}, volume_shift_list = [], cmd_lines = []; + + result_lines = mtw_execute_diskpart(['list volume']); + for (i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + Volume 0 D CD-ROM 0 B No Media + Volume 1 ???? NTFS Partition 100 MB Healthy System + Volume 2 C NTFS Partition 19 GB Healthy Boot + Volume 3 RAW Partition 9 GB Healthy + */ + match = line.match(/\s(\d)\s+([D-Z])\s.+\d+\s+[GMK]?B/i); + if (match) { + volume_no = match[1]; + letter = match[2].toUpperCase(); + if (volume_map.hasOwnProperty(letter)) { + return false; + } + volume_map[letter] = volume_no; + } + } + + charcode = 'D'.charCodeAt(0); + charcode += letter_offset; + letter_set = String.fromCharCode(charcode); + if (volume_map.hasOwnProperty(letter_set) && volume_map[letter_set] == volume_no_set) { + return true; + } + charcode_max = 'Z'.charCodeAt(0); + while (true) { + letter = String.fromCharCode(charcode); + if (!volume_map.hasOwnProperty(letter)) { + break; + } + if (charcode >= charcode_max) { + return false; + } + volume_shift_list.push({ + 'volume_no': volume_map[letter], + 'charcode_next': charcode + 1 + }); + charcode++; + } + if (volume_shift_list.length > 0) { + volume_shift_list.sort(function(a, b) { + return b.charcode_next - a.charcode_next; + }); + for (i = 0, len = volume_shift_list.length; i < len; i++) { + volume_no = volume_shift_list[i].volume_no; + charcode = volume_shift_list[i].charcode_next; + cmd_lines.push( + 'select volume=' + volume_no, + 'assign letter=' + String.fromCharCode(charcode) + ); + } + } + cmd_lines.push( + 'select volume=' + volume_no_set, + 'assign letter=' + letter_set + ); + mtw_execute_diskpart(cmd_lines); + + return true; +} + +function mtw_wait_loop(total_ms, step_ms, callback) { + while (true) { + if (callback()) { + break; + } + if (total_ms < step_ms) { + break; + } + total_ms -= step_ms; + WScript.Sleep(step_ms); + } +} + +function mtw_get_disk_list_wait() { + var disk_list_ret = []; + + /* http://support.microsoft.com/kb/870912 */ + mtw_wait_loop(5000, 500, function() { + var i, j, disk_list, disk, partition; + disk_list = mtw_get_disk_list(); + for (i = 0; i < disk_list.length; i++) { + disk = disk_list[i]; + for (j = 0; j < disk.partition_list.length; j++) { + partition = disk.partition_list[j]; + if (partition.partition_type == MTW_PARTITION_TYPE_NODATA) { + return false; + } + } + } + disk_list_ret = disk_list; + return true; + }); + + return disk_list_ret; +} + +function mtw_get_volume_list_wait(disk_no) { + var volume_list_ret = []; + + mtw_wait_loop(5000, 500, function() { + var volume_list = mtw_get_volume_list(disk_no); + if (volume_list.length > 0) { + volume_list_ret = volume_list; + return true; + } + return false; + }); + + return volume_list_ret; +} + +function mtw_mount_disk() { + var disk_list, disk_list_mounted, disk, partition, volume_list; + var do_mount, do_create, do_delete; + var cmd_lines, letter_offset = 0; + + disk_list = mtw_get_disk_list_wait(); + disk_list_mounted = []; + for (var i = 0, len = disk_list.length; i < len; i++) { + disk = disk_list[i]; + partition = null; + do_mount = do_create = do_delete = false; + if (disk.partition_list.length == 0) { + do_mount = true; + do_create = true; + } else if (disk.partition_list.length == 1) { + partition = disk.partition_list[0]; + switch (partition.partition_type) { + case '06': // DOS 3.31+ 16-bit FAT (over 32M) + case '07': // Windows NT NTFS + do_mount = true; + break; + case '83': // Linux native partition + do_mount = true; + do_delete = true; + do_create = true; + break; + default: + break; + } + } + if (!do_mount) { + continue; + } + cmd_lines = [ + 'select disk=' + disk.disk_no, + 'online disk', + 'attributes disk clear readonly' + ]; + mtw_execute_diskpart(cmd_lines); + if (do_create) { + cmd_lines = ['select disk=' + disk.disk_no]; + if (partition && do_delete) { + cmd_lines.push( + 'select partition=' + partition.partition_no, + 'delete partition' + ); + } + cmd_lines.push('create partition primary'); + mtw_execute_diskpart(cmd_lines); + } + disk_list_mounted.push(disk); + } + + for (i = 0, len = disk_list_mounted.length; i < len; i++) { + disk = disk_list_mounted[i]; + if (i == 0) { + volume_list = mtw_get_volume_list_wait(disk.disk_no); + } else { + volume_list = mtw_get_volume_list(disk.disk_no); + } + if (volume_list.length == 1) { + mtw_assign_volume_letter(volume_list[0].volume_no, letter_offset); + letter_offset += 1; + } + } +} + +function mtw_main() { + var exec_helper, args = WScript.Arguments, debug_path = ''; + + for (var i = 0, len = args.length; i < len; i++) { + if (args(i) == '--debug') { + if (i < len) { + i += 1; + debug_path = args(i); + } + } + } + + if (debug_path) { + mtw_prepare_debug(debug_path); + } + + /* http://support.microsoft.com/kb/937252 */ + exec_helper = mtw_create_shell().Exec('diskpart'); + try { + mtw_mount_disk(); + } catch (e) { + // nothing + } + exec_helper.StdIn.WriteLine('exit'); + while (exec_helper.Status == 0) { + WScript.Sleep(100); + } +} + +mtw_main(); + +` diff --git a/pkg/hostman/guestfs/kvmpart.go b/pkg/hostman/guestfs/kvmpart.go new file mode 100644 index 0000000000..6f990897dd --- /dev/null +++ b/pkg/hostman/guestfs/kvmpart.go @@ -0,0 +1,212 @@ +package guestfs + +import ( + "fmt" + "os" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +type SKVMGuestDiskPartition struct { + *SLocalGuestFS + partDev string + fs string + + readonly bool +} + +func NewKVMGuestDiskPartition(devPath string) *SKVMGuestDiskPartition { + var res = new(SKVMGuestDiskPartition) + res.partDev = devPath + res.fs = res.getFsFormat() + fileutils2.CleanFailedMountpoints() + mountPath := fmt.Sprintf("/tmp/%s", strings.Replace(devPath, "/", "_", -1)) + res.SLocalGuestFS = NewLocalGuestFS(mountPath) + return res +} + +func (p *SKVMGuestDiskPartition) IsReadonly() bool { + return IsPartitionReadonly(p) +} + +func (p *SKVMGuestDiskPartition) getFsFormat() string { + return fileutils2.GetFsFormat(p.partDev) +} + +func (p *SKVMGuestDiskPartition) Mount() bool { + if len(p.fs) == 0 || utils.IsInStringArray(p.fs, []string{"swap", "btrfs"}) { + return false + } + err := p.fsck() + if err != nil { + log.Errorf("SKVMGuestDiskPartition fsck error: %s", err) + return false + } + err = p.mount(false) + if err != nil { + log.Errorf("SKVMGuestDiskPartition mount error: %s", err) + } + + if p.IsReadonly() { + log.Errorf("SKVMGuestDiskPartition %s is readonly, try mount as ro", p.partDev) + p.Umount() + err = p.mount(true) + if err != nil { + log.Errorf("SKVMGuestDiskPartition mount as ro error %s", err) + return false + } else { + p.readonly = true + } + } + return true +} + +func (p *SKVMGuestDiskPartition) mount(readonly bool) error { + if _, err := procutils.NewCommand("mkdir", "-p", p.mountPath).Run(); err != nil { + return err + } + var cmds = []string{"mount", "-t"} + var opt, fsType string + if readonly { + opt = "ro" + } + fsType = p.fs + if fsType == "ntfs" { + fsType = "ntfs-3g" + if !readonly { + opt = "recover,remove_hiberfile,noatime,windows_names" + } + } else if fsType == "hfsplus" && !readonly { + opt = "force,rw" + } + cmds = append(cmds, fsType) + if len(opt) > 0 { + cmds = append(cmds, "-o", opt) + } + cmds = append(cmds, p.partDev, p.mountPath) + _, err := procutils.NewCommand(cmds[0], cmds[1:]...).Run() + return err +} + +func (p *SKVMGuestDiskPartition) fsck() error { + var checkCmd, fixCmd []string + switch p.fs { + case "hfsplus": + checkCmd = []string{"fsck.hfsplus", "-q", p.partDev} + fixCmd = []string{"fsck.hfsplus", "-fpy", p.partDev} + case "ext2", "ext3", "ext4": + checkCmd = []string{"e2fsck", "-n", p.partDev} + fixCmd = []string{"e2fsck", "-fp", p.partDev} + case "ntfs": + checkCmd = []string{"ntfsfix", "-n", p.partDev} + fixCmd = []string{"ntfsfix", p.partDev} + } + if len(checkCmd) > 0 { + _, err := procutils.NewCommand(checkCmd[0], checkCmd[1:]...).Run() + if err != nil { + log.Warningf("FS %s dirty, try to repair ...", p.partDev) + for i := 0; i < 3; i++ { + _, err := procutils.NewCommand(fixCmd[0], fixCmd[1:]...).Run() + if err == nil { + break + } else { + continue + } + } + } + } + return nil +} + +func (p *SKVMGuestDiskPartition) Exists(sPath string, caseInsensitive bool) bool { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return fileutils2.Exists(sPath) + } + return false +} + +func (p *SKVMGuestDiskPartition) IsMounted() bool { + if !fileutils2.Exists(p.mountPath) { + return false + } + _, err := procutils.NewCommand("mountpoint", p.mountPath).Run() + if err == nil { + return true + } else { + log.Errorln(err) + } + return false +} + +func (p *SKVMGuestDiskPartition) Umount() bool { + if p.IsMounted() { + var tries = 0 + for tries < 10 { + tries += 1 + _, err := procutils.NewCommand("umount", p.mountPath).Run() + if err == nil { + procutils.NewCommand("blockdev", "--flushbufs", p.partDev).Run() + os.Remove(p.mountPath) + return true + } else { + time.Sleep(time.Second * 1) + } + } + } + return false +} + +func (p *SKVMGuestDiskPartition) Zerofree() { + if !p.IsMounted() { + switch p.fs { + case "swap": + p.zerofreeSwap() + case "ext2", "ext3", "ext4": + p.zerofreeExt() + case "ntfs": + p.zerofreeNtfs() + } + } +} + +func (p *SKVMGuestDiskPartition) zerofreeSwap() { + uuids := fileutils2.GetDevUuid(p.partDev) + _, err := procutils.NewCommand("shred", "-n", "0", "-z", p.partDev).Run() + if err != nil { + log.Errorf("zerofree swap error: %s", err) + return + } + cmd := []string{"mkswap"} + if uuid, ok := uuids["UUID"]; ok { + cmd = append(cmd, "-U", uuid) + } + cmd = append(cmd, p.partDev) + _, err = procutils.NewCommand(cmd[0], cmd[1:]...).Run() + if err != nil { + log.Errorf("zerofree swap error: %s", err) + } +} + +func (p *SKVMGuestDiskPartition) zerofreeExt() { + _, err := procutils.NewCommand("zerofree", p.partDev).Run() + if err != nil { + log.Errorf("zerofree ext error: %s", err) + return + } +} + +func (p *SKVMGuestDiskPartition) zerofreeNtfs() { + _, err := procutils.NewCommand("ntfswipe", "-f", "-l", "-m", "-p", "-s", "-q", + p.partDev).Run() + if err != nil { + log.Errorf("zerofree ntfs error: %s", err) + return + } +} diff --git a/pkg/hostman/guestfs/localfs.go b/pkg/hostman/guestfs/localfs.go new file mode 100644 index 0000000000..6be45b04b4 --- /dev/null +++ b/pkg/hostman/guestfs/localfs.go @@ -0,0 +1,227 @@ +package guestfs + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "strings" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +type SLocalGuestFS struct { + mountPath string +} + +func (f *SLocalGuestFS) SupportSerialPorts() bool { + return false +} + +func (f *SLocalGuestFS) GetLocalPath(sPath string, caseInsensitive bool) string { + var fullPath = f.mountPath + pathSegs := strings.Split(sPath, "/") + for _, seg := range pathSegs { + if len(seg) > 0 { + var realSeg string + files, _ := ioutil.ReadDir(fullPath) + for _, file := range files { + var f = file.Name() + if f == seg || (caseInsensitive && strings.ToLower(f) == strings.ToLower(seg)) || + (seg[len(seg)-1] == '*' && (strings.HasPrefix(f, seg[:len(seg)-1]) || + (caseInsensitive && strings.HasPrefix(strings.ToLower(f), + strings.ToLower(seg[:len(seg)-1]))))) { + realSeg = f + break + } + } + if len(realSeg) > 0 { + fullPath = path.Join(fullPath, realSeg) + } else { + return "" + } + } + } + return fullPath +} + +func (f *SLocalGuestFS) Remove(path string, caseInsensitive bool) { + path = f.GetLocalPath(path, caseInsensitive) + if len(path) > 0 { + os.Remove(path) + } +} + +func (f *SLocalGuestFS) Mkdir(sPath string, mode int, caseInsensitive bool) error { + segs := strings.Split(sPath, "/") + sPath = "" + pPath := f.GetLocalPath("/", caseInsensitive) + for _, s := range segs { + if len(s) > 0 { + sPath = path.Join(sPath, s) + vPath := f.GetLocalPath(sPath, caseInsensitive) + if len(vPath) == 0 { + if err := os.Mkdir(path.Join(pPath, s), os.FileMode(mode)); err != nil { + return err + } + pPath = f.GetLocalPath(sPath, caseInsensitive) + } else { + pPath = vPath + } + } + } + return nil +} + +func (f *SLocalGuestFS) ListDir(sPath string, caseInsensitive bool) []string { + sPath = f.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + files, err := ioutil.ReadDir(sPath) + if err != nil { + log.Errorln(err) + return nil + } + var res = make([]string, 0) + for _, file := range files { + res = append(res, file.Name()) + } + return res + } + return nil +} + +func (f *SLocalGuestFS) Cleandir(dir string, keepdir, caseInsensitive bool) error { + sPath := f.GetLocalPath(dir, caseInsensitive) + if len(sPath) > 0 { + return fileutils2.Cleandir(sPath, keepdir) + } + return fmt.Errorf("No such file %s", sPath) +} + +func (f *SLocalGuestFS) Zerofiles(dir string, caseInsensitive bool) error { + sPath := f.GetLocalPath(dir, caseInsensitive) + if len(sPath) > 0 { + return fileutils2.Zerofiles(sPath) + } + return fmt.Errorf("No such file %s", sPath) +} + +func (f *SLocalGuestFS) Passwd(account, password string, caseInsensitive bool) error { + var proc = exec.Command("chroot", f.mountPath, "passwd", account) + stdin, err := proc.StdinPipe() + if err != nil { + return err + } + defer stdin.Close() + + outb, err := proc.StdoutPipe() + if err != nil { + return err + } + defer outb.Close() + + errb, err := proc.StderrPipe() + if err != nil { + return err + } + defer errb.Close() + + if err := proc.Start(); err != nil { + return err + } + io.WriteString(stdin, fmt.Sprintf("%s\n", password)) + io.WriteString(stdin, fmt.Sprintf("%s\n", password)) + stdoutPut, err := ioutil.ReadAll(outb) + if err != nil { + return err + } + stderrOutPut, err := ioutil.ReadAll(errb) + if err != nil { + return err + } + log.Infof("Passwd %s %s", stdoutPut, stderrOutPut) + return proc.Wait() +} + +func (f *SLocalGuestFS) Stat(usrDir string, caseInsensitive bool) os.FileInfo { + sPath := f.GetLocalPath(usrDir, caseInsensitive) + if len(sPath) > 0 { + fileInfo, err := os.Stat(sPath) + if err != nil { + log.Errorln(err) + } + return fileInfo + } + return nil +} + +func (f *SLocalGuestFS) Exists(sPath string, caseInsensitive bool) bool { + sPath = f.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return fileutils2.Exists(sPath) + } + return false +} + +func (f *SLocalGuestFS) Chown(sPath string, uid, gid int, caseInsensitive bool) error { + sPath = f.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return os.Chown(sPath, uid, gid) + } + return nil +} + +func (f *SLocalGuestFS) Chmod(sPath string, mode uint32, caseInsensitive bool) error { + sPath = f.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return os.Chmod(sPath, os.FileMode(mode)) + } + return nil +} + +func (f *SLocalGuestFS) UserAdd(user string, caseInsensitive bool) error { + output, err := procutils.NewCommand("chroot", f.mountPath, "useradd", "-m", "-s", "/bin/bash", user).Run() + if err != nil { + log.Errorf("Useradd fail: %s, %s", err, output) + return fmt.Errorf("%s", output) + } else { + log.Infof("Useradd: %s", output) + } + return nil +} + +func (f *SLocalGuestFS) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) { + sPath = f.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return ioutil.ReadFile(sPath) + } + return nil, fmt.Errorf("Cann't find local path") +} + +func (f *SLocalGuestFS) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error { + sFilePath := f.GetLocalPath(sPath, caseInsensitive) + if len(sFilePath) > 0 { + sPath = sFilePath + } else { + dirPath := f.GetLocalPath(path.Dir(sPath), caseInsensitive) + if len(dirPath) > 0 { + sPath = path.Join(dirPath, path.Base(sPath)) + } + } + if len(sPath) > 0 { + return fileutils2.FilePutContents(sPath, content, modAppend) + } else { + return fmt.Errorf("Cann't put content") + } +} + +func NewLocalGuestFS(mountPath string) *SLocalGuestFS { + var ret = new(SLocalGuestFS) + ret.mountPath = mountPath + return ret +} diff --git a/pkg/hostman/guestfs/sshpart/doc.go b/pkg/hostman/guestfs/sshpart/doc.go new file mode 100644 index 0000000000..ecda36719d --- /dev/null +++ b/pkg/hostman/guestfs/sshpart/doc.go @@ -0,0 +1 @@ +package sshpart // import "yunion.io/x/onecloud/pkg/hostman/guestfs/sshpart" diff --git a/pkg/hostman/guestfs/sshpart/sshpart.go b/pkg/hostman/guestfs/sshpart/sshpart.go new file mode 100644 index 0000000000..d31469d848 --- /dev/null +++ b/pkg/hostman/guestfs/sshpart/sshpart.go @@ -0,0 +1,500 @@ +package sshpart + +import ( + "fmt" + "os" + "path" + "regexp" + "strconv" + "strings" + "syscall" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/util/ssh" + stringutils "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SSHPartition struct { + term *ssh.Client + partDev string + mountPath string +} + +func NewSSHPartition(term *ssh.Client, dev string) *SSHPartition { + p := new(SSHPartition) + p.term = term + p.partDev = dev + p.mountPath = fmt.Sprintf("/tmp/%s", strings.Replace(p.partDev, "/", "_", -1)) + return p +} + +func (p *SSHPartition) GetMountPath() string { + return p.mountPath +} + +func (p *SSHPartition) GetFsFormat() (string, error) { + cmd := fmt.Sprintf("/lib/mos/partfs.sh %s", p.partDev) + ret, err := p.term.Run(cmd) + if err != nil { + return "", err + } + return strings.TrimSpace(ret[0]), nil +} + +func (p *SSHPartition) osChmod(path string, mode uint32) error { + cmd := fmt.Sprintf("chmod %o %s", (mode & 0777), path) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) osMkdirP(dir string, mode uint32) error { + cmd := fmt.Sprintf("mkdir -p %s", dir) + _, err := p.term.Run(cmd) + if err != nil { + return err + } + if mode != 0 { + return p.osChmod(dir, mode) + } + return nil +} + +func (p *SSHPartition) Mkdir(sPath string, mode int, caseInsensitive bool) error { + segs := strings.Split(sPath, "/") + sp := "" + pPath := p.GetLocalPath("/", caseInsensitive) + var err error + for _, s := range segs { + if len(s) > 0 { + sp = path.Join(sp, s) + vPath := p.GetLocalPath(sp, caseInsensitive) + if len(vPath) == 0 { + err = p.osMkdirP(path.Join(pPath, s), uint32(mode)) + pPath = p.GetLocalPath(sp, caseInsensitive) + } else { + pPath = vPath + } + } + } + return err +} + +func (p *SSHPartition) osRmDir(path string) error { + _, err := p.term.Run(fmt.Sprintf("rm -fr %s", path)) + return err +} + +func (p *SSHPartition) osPathExists(path string) bool { + _, err := p.term.Run(fmt.Sprintf("test -e %s", path)) + if err != nil { + return false + } + return true +} + +func (p *SSHPartition) Mount() bool { + if err := p.osMkdirP(p.mountPath, 0); err != nil { + log.Errorf("SSHPartition mount error: %s", err) + return false + } + fs, err := p.GetFsFormat() + if err != nil { + log.Errorf("SSHPartition mount error: %s", err) + return false + } + fstr := "" + if fs == "ntfs" { + fstr = "-t ntfs-3g" + } + cmd := fmt.Sprintf("mount %s -o sync %s %s", fstr, p.partDev, p.mountPath) + log.Infof("Do mount %s", cmd) + _, err = p.term.Run(cmd) + if err != nil { + p.osRmDir(p.mountPath) + log.Errorf("SSHPartition mount error: %s", err) + return false + } + return true +} + +func (p *SSHPartition) Umount() bool { + if !p.IsMounted() { + log.Errorf("%s is not mounted", p.mountPath) + return false + } + var err error + for tries := 0; tries < 10; tries++ { + cmds := []string{ + "sync", + "/sbin/sysctl -w vm.drop_caches=3", + fmt.Sprintf("/bin/umount %s", p.mountPath), + fmt.Sprintf("/sbin/hdparm -f %s", p.partDev), + } + _, err = p.term.Run(cmds...) + if err != nil { + log.Errorf("umount %s error: %v", p.mountPath, err) + time.Sleep(1 * time.Second) + } else { + return true + } + } + return err == nil +} + +func (p *SSHPartition) IsMounted() bool { + if !p.osPathExists(p.mountPath) { + return false + } + _, err := p.term.Run(fmt.Sprintf("mountpoint %s", p.mountPath)) + if err != nil { + return false + } + return true +} + +func (p *SSHPartition) Chmod(sPath string, mode uint32, caseI bool) error { + sPath = p.GetLocalPath(sPath, caseI) + if sPath != "" { + return p.osChmod(sPath, mode) + } + return nil +} + +func (p *SSHPartition) osIsDir(path string) bool { + _, err := p.term.Run(fmt.Sprintf("test -d %s", path)) + if err != nil { + return false + } + return true +} + +func (p *SSHPartition) osListDir(path string) ([]string, error) { + if !p.osIsDir(path) { + return nil, fmt.Errorf("Path %s is not dir", path) + } + ret, err := p.term.Run(fmt.Sprintf("ls -a %s", path)) + if err != nil { + return nil, err + } + files := []string{} + for _, f := range ret { + f = strings.TrimSpace(f) + if !utils.IsInStringArray(f, []string{"", ".", ".."}) { + files = append(files, f) + } + } + return files, nil +} + +func (p *SSHPartition) GetLocalPath(sPath string, caseI bool) string { + var fullPath = p.mountPath + pathSegs := strings.Split(sPath, "/") + for _, seg := range pathSegs { + if len(seg) > 0 { + var realSeg string + files, err := p.osListDir(fullPath) + if err != nil { + log.Errorf("List dir %s error: %v", sPath, err) + return "" + } + for _, f := range files { + if f == seg || (caseI && (strings.ToLower(f)) == strings.ToLower(seg)) { + realSeg = f + break + } + } + if len(realSeg) > 0 { + fullPath = path.Join(fullPath, realSeg) + } else { + return "" + } + } + } + return fullPath +} + +func (p *SSHPartition) Exists(sPath string, caseInsensitive bool) bool { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return p.osPathExists(sPath) + } + return false +} + +func (p *SSHPartition) sshFileGetContents(path string) ([]byte, error) { + cmd := fmt.Sprintf("cat %s", path) + ret, err := p.term.Run(cmd) + if err != nil { + return nil, err + } + if len(ret) > 0 && ret[len(ret)-1] == "" { + ret = ret[0 : len(ret)-1] + } + retBytes := []byte(strings.Join(ret, "\n")) + return retBytes, nil +} + +func (p *SSHPartition) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return p.sshFileGetContents(sPath) + } + return nil, fmt.Errorf("Cann't find path: %s", sPath) +} + +func (p *SSHPartition) sshFilePutContents(sPath, content string, modAppend bool) error { + op := ">" + if modAppend { + op = ">>" + } + + if len(content) == 0 { + _, err := p.term.Run(fmt.Sprintf("echo '' %s %s", op, sPath)) + return err + } + + cmds := []string{} + var chunkSize int = 8192 + for offset := 0; offset < len(content); offset += chunkSize { + end := offset + chunkSize + if end > len(content) { + end = len(content) + } + ll, err := stringutils.EscapeEchoString(content[offset:end]) + if err != nil { + return fmt.Errorf("EscapeEchoString %q error: %v", content[offset:end], err) + } + cmd := fmt.Sprintf("echo -n -e \"%s\" %s %s", ll, op, sPath) + cmds = append(cmds, cmd) + if op == ">" { + op = ">>" + } + } + _, err := p.term.Run(cmds...) + return err +} + +func (p *SSHPartition) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error { + sFilePath := p.GetLocalPath(sPath, caseInsensitive) + if len(sFilePath) > 0 { + sPath = sFilePath + } else { + dirPath := p.GetLocalPath(path.Dir(sPath), caseInsensitive) + if len(dirPath) > 0 { + sPath = path.Join(dirPath, path.Base(sPath)) + } + } + if len(sPath) > 0 { + return p.sshFilePutContents(sPath, content, modAppend) + } + return fmt.Errorf("Can't put content to %s", sPath) +} + +func (p *SSHPartition) ListDir(sPath string, caseInsensitive bool) []string { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + ret, err := p.osListDir(sPath) + if err != nil { + log.Errorf("list dir for %s: %v", sPath, err) + return nil + } + return ret + } + return nil +} + +func (p *SSHPartition) osChown(sPath string, uid, gid int) error { + cmd := fmt.Sprintf("chown %d.%d %s", uid, gid, sPath) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) Chown(sPath string, uid, gid int, caseInsensitive bool) error { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) == 0 { + return fmt.Errorf("Can't get local path: %s", sPath) + } + return p.osChown(sPath, uid, gid) +} + +func (p *SSHPartition) osRemove(sPath string) error { + cmd := fmt.Sprintf("rm %s", sPath) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) Remove(sPath string, caseInsensitive bool) { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + p.osRemove(sPath) + } +} + +func (p *SSHPartition) UserAdd(user string, caseInsensitive bool) error { + cmd := fmt.Sprintf("/usr/sbin/chroot %s /usr/sbin/useradd -m -s /bin/bash %s", p.mountPath, user) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) Passwd(user, password string, caseInsensitive bool) error { + newpass := "/tmp/newpass" + p.sshFilePutContents(newpass, fmt.Sprintf("%s\n%s\n", password, password), false) + cmd := fmt.Sprintf("/usr/sbin/chroot %s /usr/bin/passwd %s < %s", p.mountPath, user, newpass) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) osStat(sPath string) (os.FileInfo, error) { + cmd := fmt.Sprintf("ls -a -l -n -i -s -d %s", sPath) + ret, err := p.term.Run(cmd) + if err != nil { + return nil, err + } + for _, line := range ret { + dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1) + if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) || + (dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) { + stMode, err := modeStr2Bin(dat[2]) + if err != nil { + return nil, err + } + stIno, _ := strconv.Atoi(dat[0]) + stUid, _ := strconv.Atoi(dat[4]) + stGid, _ := strconv.Atoi(dat[5]) + stSize, _ := strconv.Atoi(dat[6]) + info := &sFileInfo{ + name: sPath, + size: int64(stSize), + mode: os.FileMode(stMode), + isDir: dat[2][0] == 'd', + stat: &syscall.Stat_t{ + Ino: uint64(stIno), + Uid: uint32(stUid), + Gid: uint32(stGid), + Size: int64(stSize), + }, + } + return info, nil + } + } + return nil, fmt.Errorf("Can't stat for path %s", sPath) +} + +func modeStr2Bin(mode string) (uint32, error) { + table := []map[byte]uint32{ + {'-': syscall.S_IRUSR, 'd': syscall.S_IFDIR, 'l': syscall.S_IFLNK}, + {'r': syscall.S_IRUSR}, + {'w': syscall.S_IWUSR}, + {'x': syscall.S_IXUSR, 's': syscall.S_ISUID}, + {'r': syscall.S_IRGRP}, + {'w': syscall.S_IWGRP}, + {'x': syscall.S_IXGRP, 's': syscall.S_ISGID}, + {'r': syscall.S_IROTH}, + {'w': syscall.S_IWOTH}, + {'x': syscall.S_IXOTH}, + } + if len(mode) != len(table) { + return 0, fmt.Errorf("Invalid mod %q", mode) + } + var ret uint32 = 0 + for i := 0; i < len(table); i++ { + ret |= table[i][mode[i]] + } + return ret, nil +} + +// sFileInfo implements os.FileInfo interface +type sFileInfo struct { + name string + size int64 + mode os.FileMode + isDir bool + stat *syscall.Stat_t +} + +func (info sFileInfo) Name() string { + return info.name +} + +func (info sFileInfo) Size() int64 { + return info.size +} + +func (info sFileInfo) Mode() os.FileMode { + return info.mode +} + +func (info sFileInfo) IsDir() bool { + return info.isDir +} + +func (info sFileInfo) ModTime() time.Time { + // TODO: impl + return time.Now() +} + +func (info sFileInfo) Sys() interface{} { + return info.stat +} + +func (p *SSHPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo { + sPath = p.GetLocalPath(sPath, caseInsensitive) + if len(sPath) == 0 { + return nil + } + info, err := p.osStat(sPath) + if err != nil { + log.Errorf("stat %s error: %v", sPath, err) + return nil + } + return info +} + +func (p *SSHPartition) Zerofiles(dir string, caseI bool) error { + return nil +} + +func (p *SSHPartition) GetReadonly() bool { + return false +} + +func (p *SSHPartition) SupportSerialPorts() bool { + return true +} + +func (p *SSHPartition) Cleandir(dir string, keepdir, caseInsensitive bool) error { + return nil +} + +func MountSSHRootfs(term *ssh.Client, layouts []baremetal.Layout) (*SSHPartition, fsdriver.IRootFsDriver, error) { + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + if err := tool.RetrieveDiskInfo(); err != nil { + return nil, nil, err + } + tool.RetrievePartitionInfo() + parts := tool.GetPartitions() + if len(parts) == 0 { + return nil, nil, fmt.Errorf("Not found partitions") + } + for _, part := range parts { + dev := NewSSHPartition(term, part.GetDev()) + if !dev.Mount() { + continue + } + if rootFs := guestfs.DetectRootFs(dev); rootFs != nil { + log.Infof("Use class %#v", rootFs) + return dev, rootFs, nil + } else { + dev.Umount() + } + } + return nil, nil, fmt.Errorf("Fail to find rootfs") +} diff --git a/pkg/hostman/guestfs/sshpart/sshpart_test.go b/pkg/hostman/guestfs/sshpart/sshpart_test.go new file mode 100644 index 0000000000..203fd235dc --- /dev/null +++ b/pkg/hostman/guestfs/sshpart/sshpart_test.go @@ -0,0 +1,43 @@ +package sshpart + +import ( + //"syscall" + "testing" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +var defaultSSHClient *ssh.Client + +func init() { + var err error + defaultSSHClient, err = ssh.NewClient("192.168.0.254", 22, "root", "oYA79tAcs4A1X4uT", "") + if err != nil { + log.Fatalf("Connect to ssh client error: %v", err) + } +} + +func TestNewSSHPartition(t *testing.T) { + dev := NewSSHPartition(defaultSSHClient, "/dev/sda2") + //err := dev.Mount() + //if err != nil { + //t.Errorf("Mount error: %v", err) + //return + //} + //defer func() { dev.Unmount() }() + log.Infof("%v", dev.Exists("/etc/", false)) + log.Infof("%v", dev.ListDir("/", false)) + dev.Mkdir("/tmp/test123", 0777, false) + dev.FilePutContents("/tmp/test123/content", "test1234\nhhhh", true, false) + pubkeys := &sshkeys.SSHKeys{ + PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz0BLJD+xXYd3AP26uFs42mQSoznPew6gC84P9eUEAJHdkT/8WqTJV0z9M8ZU+8UbuR3iTSbblatrZepPkU2KkvE9ZkFftCIGCWCgvRWFfrDdMF1jwGYtKDg1xVxCmxzTgR+NCuE7HIyDsNL/IKbIVH6QMCxwAIdxHrAT4WdVvkDrD5ihSmIMgnmbCSidok8N7l9zECN54EccV3LGaABumtO5Y7Um7HRm+gdc6esg3HTkIXW402w92zaeHaqm4EGek/FB24WhIcwSErMhXnnHPoAATNzWD+3RQZo2po+95FE/oZw7QO7hG9lWmCDYpJNim+Ix35ftYs1j1S4hray3z lzx@lzx-t470p", + } + err := fsdriver.DeployAuthorizedKeys(dev, "/home/cloudroot", pubkeys, true) + if err != nil { + log.Errorf("Deploy keys error: %v", err) + } +} diff --git a/pkg/hostman/guesthandlers/doc.go b/pkg/hostman/guesthandlers/doc.go new file mode 100644 index 0000000000..65e2037dd1 --- /dev/null +++ b/pkg/hostman/guesthandlers/doc.go @@ -0,0 +1 @@ +package guesthandlers // import "yunion.io/x/onecloud/pkg/hostman/guesthandlers" diff --git a/pkg/hostman/guesthandlers/guesthandler.go b/pkg/hostman/guesthandlers/guesthandler.go new file mode 100644 index 0000000000..ffddfc4b33 --- /dev/null +++ b/pkg/hostman/guesthandlers/guesthandler.go @@ -0,0 +1,427 @@ +package guesthandlers + +import ( + "context" + "fmt" + "net/http" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/hostman/guestman" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/auth" +) + +type strDict map[string]string +type actionFunc func(context.Context, string, jsonutils.JSONObject) (interface{}, error) + +var ( + keyWords = []string{"servers"} + actionFuncs = map[string]actionFunc{ + "create": guestCreate, + "deploy": guestDeploy, + "start": guestStart, + "stop": guestStop, + "monitor": guestMonitor, + "sync": guestSync, + "suspend": guestSuspend, + + "snapshot": guestSnapshot, + "delete-snapshot": guestDeleteSnapshot, + "reload-disk-snapshot": guestReloadDiskSnapshot, + // "remove-statefile": guestRemoveStatefile, + // "io-throttle": guestIoThrottle, + + "src-prepare-migrate": guestSrcPrepareMigrate, + "dest-prepare-migrate": guestDestPrepareMigrate, + "live-migrate": guestLiveMigrate, + "resume": guestResume, + // "start-nbd-server": guestStartNbdServer, + "drive-mirror": guestDriveMirror, + } +) + +func AddGuestTaskHandler(prefix string, app *appsrv.Application) { + for _, keyWord := range keyWords { + app.AddHandler("GET", + fmt.Sprintf("%s/%s//status", prefix, keyWord), + auth.Authenticate(getStatus)) + + app.AddHandler("POST", + fmt.Sprintf("%s/%s/cpu-node-balance", prefix, keyWord), + auth.Authenticate(cpusetBalance)) + + app.AddHandler("POST", + fmt.Sprintf("%s/%s//", prefix, keyWord), + auth.Authenticate(guestActions)) + + app.AddHandler("DELETE", + fmt.Sprintf("%s/%s/", prefix, keyWord), + auth.Authenticate(deleteGuest)) + } +} + +func guestActions(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + if body == nil { + body = jsonutils.NewDict() + } + var sid = params[""] + var action = params[""] + if f, ok := actionFuncs[action]; !ok { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("%s Not found", action)) + } else { + log.Infof("Guest %s Do %s", sid, action) + res, err := f(ctx, sid, body) + if err != nil { + hostutils.Response(ctx, w, err) + } else if res != nil { + hostutils.Response(ctx, w, res) + } else { + hostutils.ResponseOk(ctx, w) + } + } +} + +func getStatus(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, _ := appsrv.FetchEnv(ctx, w, r) + var status = guestman.GetGuestManager().Status(params[""]) + appsrv.SendStruct(w, strDict{"status": status}) +} + +func cpusetBalance(ctx context.Context, w http.ResponseWriter, r *http.Request) { + hostutils.DelayTask(ctx, guestman.GetGuestManager().CpusetBalance, nil) + hostutils.ResponseOk(ctx, w) +} + +func deleteGuest(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + var sid = params[""] + var migrated bool + if body != nil { + migrated = jsonutils.QueryBoolean(body, "migrated", false) + } + guest, err := guestman.GetGuestManager().Delete(sid) + if err != nil { + hostutils.Response(ctx, w, err) + } else { + hostutils.DelayTask(ctx, guest.CleanGuest, migrated) + hostutils.Response(ctx, w, map[string]bool{"delay_clean": true}) + } +} + +func guestCreate(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + err := guestman.GetGuestManager().PrepareCreate(sid) + if err != nil { + return nil, err + } + hostutils.DelayTask(ctx, guestman.GetGuestManager().GuestDeploy, &guestman.SGuestDeploy{sid, body, true}) + return nil, nil +} + +func guestDeploy(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + err := guestman.GetGuestManager().PrepareDeploy(sid) + if err != nil { + return nil, err + } + hostutils.DelayTask(ctx, guestman.GetGuestManager().GuestDeploy, &guestman.SGuestDeploy{sid, body, false}) + return nil, nil +} + +func guestStart(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + return guestman.GetGuestManager().GuestStart(ctx, sid, body) +} + +func guestStop(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + timeout, err := body.Int("timeout") + if err != nil { + timeout = 30 + } + return nil, guestman.GetGuestManager().GuestStop(ctx, sid, timeout) +} + +func guestMonitor(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + + if body.Contains("cmd") { + var c = make(chan string) + cb := func(res string) { + c <- res + } + cmd, _ := body.GetString("cmd") + err := guestman.GetGuestManager().Monitor(sid, cmd, cb) + if err != nil { + return nil, err + } else { + var res = <-c + if len(res) > 1 { + res = res[1 : len(res)-1] + } + lines := strings.Split(res, "\\r\\n") + + return strDict{"results": strings.Join(lines, "\n")}, nil + } + } else { + return nil, httperrors.NewMissingParameterError("cmd") + } +} + +func guestSync(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + hostutils.DelayTask(ctx, guestman.GetGuestManager().GuestSync, &guestman.SBaseParms{sid, body}) + return nil, nil +} + +func guestSuspend(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().GuestSuspend, sid) + return nil, nil +} + +func guestSrcPrepareMigrate(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + liveMigrate := jsonutils.QueryBoolean(body, "live_migrate", false) + hostutils.DelayTask(ctx, guestman.GetGuestManager().SrcPrepareMigrate, + &guestman.SSrcPrepareMigrate{sid, liveMigrate}) + return nil, nil +} + +func guestDestPrepareMigrate(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().CanMigrate(sid) { + return nil, httperrors.NewBadRequestError("Guest exist") + } + desc, err := body.Get("desc") + if err != nil { + return nil, httperrors.NewMissingParameterError("desc") + } + qemuVersion, err := body.GetString("qemu_version") + if err != nil { + return nil, httperrors.NewMissingParameterError("qemu_version") + } + liveMigrate := jsonutils.QueryBoolean(body, "live_migrate", false) + isLocal, err := body.Bool("is_local_storage") + if err != nil { + return nil, httperrors.NewMissingParameterError("is_local_storage") + } + var params = &guestman.SDestPrepareMigrate{} + params.Sid = sid + params.Desc = desc + params.QemuVersion = qemuVersion + params.LiveMigrate = liveMigrate + if isLocal { + serverUrl, err := body.GetString("server_url") + if err != nil { + return nil, httperrors.NewMissingParameterError("server_url") + } else { + params.ServerUrl = serverUrl + } + snapshotsUri, err := body.GetString("snapshots_uri") + if err != nil { + return nil, httperrors.NewMissingParameterError("snapshots_uri") + } else { + params.SnapshotsUri = snapshotsUri + } + disksUri, err := body.GetString("disks_uri") + if err != nil { + return nil, httperrors.NewMissingParameterError("disks_uri") + } else { + params.DisksUri = disksUri + } + srcSnapshots, err := body.Get("src_snapshots") + if err != nil { + return nil, httperrors.NewMissingParameterError("src_snapshots") + } else { + params.SrcSnapshots = srcSnapshots + } + disksBack, err := body.Get("disks_back") + if err != nil { + return nil, httperrors.NewMissingParameterError("disks_back") + } else { + params.DisksBackingFile = disksBack + } + disks, err := desc.GetArray("disks") + if err != nil { + return nil, httperrors.NewInputParameterError("Get desc disks error") + } else { + targetStorageId, _ := disks[0].GetString("target_storage_id") + if len(targetStorageId) == 0 { + return nil, httperrors.NewInputParameterError("Disk desc missing target storage id") + } + params.TargetStorageId = targetStorageId + } + } + hostutils.DelayTask(ctx, guestman.GetGuestManager().DestPrepareMigrate, params) + return nil, nil +} + +func guestLiveMigrate(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + destPort, err := body.Int("live_migrate_dest_port") + if err != nil { + return nil, httperrors.NewMissingParameterError("live_migrate_dest_port") + } + destIp, err := body.GetString("dest_ip") + if err != nil { + return nil, httperrors.NewMissingParameterError("dest_ip") + } + isLocal, err := body.Bool("is_local_storage") + if err != nil { + return nil, httperrors.NewMissingParameterError("is_local_storage") + } + hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().LiveMigrate, &guestman.SLiveMigrate{ + Sid: sid, DestPort: int(destPort), DestIp: destIp, IsLocal: isLocal, + }) + return nil, nil +} + +func guestResume(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + isLiveMigrate := jsonutils.QueryBoolean(body, "live_migrate", false) + guestman.GetGuestManager().Resume(ctx, sid, isLiveMigrate) + return nil, nil +} + +// func guestStartNbdServer(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { +// if !guestManger.IsGuestExist(sid) { +// return nil, httperrors.NewNotFoundError("Guest %s not found", sid) +// } +// hostutils.DelayTask(ctx, guestManger.StartNbdServer, sid) +// return nil, nil +// } + +func guestDriveMirror(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + backupNbdServerUri, err := body.GetString("backup_nbd_server_uri") + if err != nil { + return nil, httperrors.NewMissingParameterError("backup_nbd_server_uri") + } + hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().StartDriveMirror, + &guestman.SDriverMirror{sid, backupNbdServerUri}) + return nil, nil +} + +func guestReloadDiskSnapshot(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + diskId, err := body.GetString("disk_id") + if err != nil { + return nil, httperrors.NewMissingParameterError("disk_id") + } + + var disk storageman.IDisk + guest := guestman.GetGuestManager().Servers[sid] + disks, _ := guest.Desc.GetArray("disks") + for _, d := range disks { + id, _ := d.GetString("disk_id") + if diskId == id { + diskPath, _ := d.GetString("path") + disk = storageman.GetManager().GetDiskByPath(diskPath) + break + } + } + if disk == nil { + return nil, httperrors.NewNotFoundError("Disk not found") + } + + hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().ReloadDiskSnapshot, &guestman.SReloadDisk{sid, disk}) + return nil, nil +} + +func guestSnapshot(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + if !guestman.GetGuestManager().IsGuestExist(sid) { + return nil, httperrors.NewNotFoundError("Guest %s not found", sid) + } + snapshotId, err := body.GetString("snapshot_id") + if err != nil { + return nil, httperrors.NewMissingParameterError("snapshot_id") + } + diskId, err := body.GetString("disk_id") + if err != nil { + return nil, httperrors.NewMissingParameterError("disk_id") + } + + var disk storageman.IDisk + guest := guestman.GetGuestManager().Servers[sid] + disks, _ := guest.Desc.GetArray("disks") + for _, d := range disks { + id, _ := d.GetString("disk_id") + if diskId == id { + diskPath, _ := d.GetString("path") + disk = storageman.GetManager().GetDiskByPath(diskPath) + break + } + } + if disk == nil { + return nil, httperrors.NewNotFoundError("Disk not found") + } + + hostutils.DelayTask(ctx, guestman.GetGuestManager().DoSnapshot, &guestman.SDiskSnapshot{sid, snapshotId, disk}) + return nil, nil +} + +func guestDeleteSnapshot(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) { + deleteSnapshot, err := body.GetString("delete_snapshot") + if err != nil { + return nil, httperrors.NewMissingParameterError("delete_snapshot") + } + diskId, err := body.GetString("disk_id") + if err != nil { + return nil, httperrors.NewMissingParameterError("disk_id") + } + + var disk storageman.IDisk + guest := guestman.GetGuestManager().Servers[sid] + disks, _ := guest.Desc.GetArray("disks") + for _, d := range disks { + id, _ := d.GetString("disk_id") + if diskId == id { + diskPath, _ := d.GetString("path") + disk = storageman.GetManager().GetDiskByPath(diskPath) + break + } + } + if disk == nil { + return nil, httperrors.NewNotFoundError("Disk not found") + } + + params := &guestman.SDeleteDiskSnapshot{ + Sid: sid, + DeleteSnapshot: deleteSnapshot, + Disk: disk, + } + + if !jsonutils.QueryBoolean(body, "auto_delete", false) { + convertSnapshot, err := body.GetString("convert_snapshot") + if err != nil { + return nil, httperrors.NewMissingParameterError("convert_snapshot") + } + params.ConvertSnapshot = convertSnapshot + pendingDelete, err := body.Bool("pending_delete") + if err != nil { + return nil, httperrors.NewMissingParameterError("pending_delete") + } + params.PendingDelete = pendingDelete + } + hostutils.DelayTask(ctx, guestman.GetGuestManager().DeleteSnapshot, params) + return nil, nil +} diff --git a/pkg/hostman/guestman/doc.go b/pkg/hostman/guestman/doc.go new file mode 100644 index 0000000000..33d4c267a5 --- /dev/null +++ b/pkg/hostman/guestman/doc.go @@ -0,0 +1 @@ +package guestman // import "yunion.io/x/onecloud/pkg/hostman/guestman" diff --git a/pkg/hostman/guestman/guesthelper.go b/pkg/hostman/guestman/guesthelper.go new file mode 100644 index 0000000000..18802d5de7 --- /dev/null +++ b/pkg/hostman/guestman/guesthelper.go @@ -0,0 +1,67 @@ +package guestman + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/hostman/storageman" +) + +type SBaseParms struct { + Sid string + Body jsonutils.JSONObject +} + +type SGuestDeploy struct { + Sid string + Body jsonutils.JSONObject + IsInit bool +} + +type SSrcPrepareMigrate struct { + Sid string + LiveMigrate bool +} + +type SDestPrepareMigrate struct { + Sid string + ServerUrl string + QemuVersion string + SnapshotsUri string + DisksUri string + TargetStorageId string + LiveMigrate bool + + Desc jsonutils.JSONObject + DisksBackingFile jsonutils.JSONObject + SrcSnapshots jsonutils.JSONObject +} + +type SLiveMigrate struct { + Sid string + DestPort int + DestIp string + IsLocal bool +} + +type SDriverMirror struct { + Sid string + NbdServerUri string +} + +type SReloadDisk struct { + Sid string + Disk storageman.IDisk +} + +type SDiskSnapshot struct { + Sid string + SnapshotId string + Disk storageman.IDisk +} + +type SDeleteDiskSnapshot struct { + Sid string + DeleteSnapshot string + Disk storageman.IDisk + ConvertSnapshot string + PendingDelete bool +} diff --git a/pkg/hostman/guestman/guestman.go b/pkg/hostman/guestman/guestman.go new file mode 100644 index 0000000000..e0eaa1eff0 --- /dev/null +++ b/pkg/hostman/guestman/guestman.go @@ -0,0 +1,677 @@ +package guestman + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path" + "strings" + "sync" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/seclib" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/cgrouputils" + "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/timeutils2" +) + +const ( + VNC_PORT_BASE = 5900 +) + +type SGuestManager struct { + host hostutils.IHost + ServersPath string + Servers map[string]*SKVMGuestInstance + CandidateServers map[string]*SKVMGuestInstance + ServersLock *sync.Mutex + + isLoaded bool +} + +func NewGuestManager(host hostutils.IHost, serversPath string) *SGuestManager { + manager := &SGuestManager{} + manager.host = host + manager.ServersPath = serversPath + manager.Servers = make(map[string]*SKVMGuestInstance, 0) + manager.CandidateServers = make(map[string]*SKVMGuestInstance, 0) + manager.ServersLock = &sync.Mutex{} + manager.StartCpusetBalancer() + manager.LoadExistingGuests() + return manager +} + +func (m *SGuestManager) Bootstrap() { + if m.isLoaded || len(m.ServersPath) == 0 { + log.Errorln("Guestman bootstrap has been called!!!!!") + } else { + m.isLoaded = true + log.Infof("Loading existing guests ...") + if len(m.CandidateServers) > 0 { + m.VerifyExistingGuests(false) + } else { + m.OnLoadExistingGuestsComplete() + } + } +} + +func (m *SGuestManager) VerifyExistingGuests(pendingDelete bool) { + params := jsonutils.NewDict() + params.Set("limit", jsonutils.NewInt(0)) + params.Set("admin", jsonutils.JSONTrue) + params.Set("system", jsonutils.JSONTrue) + params.Set("pending_delete", jsonutils.NewBool(pendingDelete)) + params.Set("get_backup_guests_on_host", jsonutils.JSONTrue) + params.Set("filter.0", jsonutils.NewString( + fmt.Sprintf("host_id.equals(%s)", m.host.GetHostId()))) + if len(m.CandidateServers) > 0 { + keys := make([]string, len(m.CandidateServers)) + var index = 0 + for k := range m.CandidateServers { + keys[index] = k + index++ + } + params.Set("filter.1", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(keys, ",")))) + } + res, err := modules.Servers.List(hostutils.GetComputeSession(context.Background()), params) + if err != nil { + m.OnVerifyExistingGuestsFail(err, pendingDelete) + } else { + m.OnVerifyExistingGuestsSucc(res.Data, pendingDelete) + } +} + +func (m *SGuestManager) OnVerifyExistingGuestsFail(err error, pendingDelete bool) { + log.Errorf("OnVerifyExistingGuestFail: %s, try again 30 seconds later", err.Error()) + timeutils2.AddTimeout(30*time.Second, func() { m.VerifyExistingGuests(false) }) +} + +func (m *SGuestManager) OnVerifyExistingGuestsSucc(servers []jsonutils.JSONObject, pendingDelete bool) { + for _, v := range servers { + id, _ := v.GetString("id") + server, ok := m.CandidateServers[id] + if !ok { + log.Errorf("verify_existing_guests return unknown server %s ???????", id) + } else { + server.ImportServer(pendingDelete) + } + } + if !pendingDelete { + m.VerifyExistingGuests(true) + } else { + var unknownServerrs = make([]*SKVMGuestInstance, 0) + for _, server := range m.CandidateServers { + log.Errorf("Server %s not found on this host", server.GetName()) + unknownServerrs = append(unknownServerrs, server) + } + for _, server := range unknownServerrs { + m.RemoveCandidateServer(server) + } + } +} + +func (m *SGuestManager) RemoveCandidateServer(server *SKVMGuestInstance) { + if _, ok := m.CandidateServers[server.Id]; ok { + delete(m.CandidateServers, server.Id) + if len(m.CandidateServers) == 0 { + m.OnLoadExistingGuestsComplete() + } + } +} + +func (m *SGuestManager) OnLoadExistingGuestsComplete() { + log.Infof("Load existing guests complete...") + err := m.host.PutHostOnline() + if err != nil { + log.Errorln(err) + } + + if !options.HostOptions.EnableCpuBinding { + m.ClenaupCpuset() + } +} + +func (m *SGuestManager) ClenaupCpuset() { + for _, guest := range m.Servers { + guest.CleanupCpuset() + } +} + +func (m *SGuestManager) StartCpusetBalancer() { + if !options.HostOptions.EnableCpuBinding { + return + } + go func() { + for { + if options.HostOptions.EnableCpuBinding { + m.cpusetBalance() + } + time.Sleep(time.Second * 120) + } + }() +} + +func (m *SGuestManager) cpusetBalance() { + cgrouputils.RebalanceProcesses(nil) +} + +func (m *SGuestManager) IsGuestDir(f os.FileInfo) bool { + if !regutils.MatchUUID(f.Name()) { + return false + } + if !f.Mode().IsDir() { + return false + } + descFile := path.Join(m.ServersPath, f.Name(), "desc") + if !fileutils2.Exists(descFile) { + return false + } + return true +} + +func (m *SGuestManager) IsGuestExist(sid string) bool { + if _, ok := guestManger.Servers[sid]; !ok { + return false + } else { + return true + } +} + +func (m *SGuestManager) LoadExistingGuests() { + files, err := ioutil.ReadDir(m.ServersPath) + if err != nil { + log.Errorf("List servers path %s error %s", m.ServersPath, err) + } + for _, f := range files { + if _, ok := m.Servers[f.Name()]; !ok && m.IsGuestDir(f) { + log.Infof("Find existing guest %s", f.Name()) + m.LoadServer(f.Name()) + } + } +} + +func (m *SGuestManager) LoadServer(sid string) { + guest := NewKVMGuestInstance(sid, m) + err := guest.LoadDesc() + if err != nil { + log.Errorf("On load server error: %s", err) + return + } + m.CandidateServers[sid] = guest +} + +//isDeleted先不加,目测只是在ofp中用到了 +func (m *SGuestManager) GetGuestNicDesc(mac, ip, port, bridge string, isCandidate bool) (jsonutils.JSONObject, jsonutils.JSONObject) { + servers := m.Servers + if isCandidate { + servers = m.CandidateServers + } + for _, guest := range servers { + if guest.IsLoaded() { + nic := guest.GetNicDescMatch(mac, ip, port, bridge) + if nic != nil { + return guest.Desc, nic + } + } + } + return nil, nil +} + +func (m *SGuestManager) PrepareCreate(sid string) error { + m.ServersLock.Lock() + defer m.ServersLock.Unlock() + if _, ok := m.Servers[sid]; ok { + return httperrors.NewBadRequestError("Guest %s exists", sid) + } + guest := NewKVMGuestInstance(sid, m) + m.Servers[sid] = guest + return guest.PrepareDir() +} + +func (m *SGuestManager) PrepareDeploy(sid string) error { + m.ServersLock.Lock() + defer m.ServersLock.Unlock() + if guest, ok := m.Servers[sid]; !ok { + return httperrors.NewBadRequestError("Guest %s not exists", sid) + } else { + if guest.IsRunning() || guest.IsSuspend() { + return httperrors.NewBadRequestError("Cannot deploy on running/suspend guest") + } + } + return nil +} + +func (m *SGuestManager) Monitor(sid, cmd string, callback func(string)) error { + if guest, ok := m.Servers[sid]; ok { + if guest.IsRunning() { + guest.Monitor.HumanMonitorCommand(cmd, callback) + return nil + } else { + return httperrors.NewBadRequestError("Server stopped??") + } + } else { + return httperrors.NewNotFoundError("Not found") + } +} + +// Delay process +func (m *SGuestManager) GuestDeploy(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + deployParams, ok := params.(*SGuestDeploy) + if !ok { + return nil, hostutils.ParamsError + } + + guest, ok := m.Servers[deployParams.Sid] + if ok { + desc, _ := deployParams.Body.Get("desc") + if desc != nil { + guest.SaveDesc(desc) + } + if jsonutils.QueryBoolean(deployParams.Body, "k8s_pod", false) { + return nil, nil + } + publicKey := sshkeys.GetKeys(deployParams.Body) + deploys, _ := deployParams.Body.GetArray("deploys") + password, _ := deployParams.Body.GetString("password") + resetPassword := jsonutils.QueryBoolean(deployParams.Body, "reset_password", false) + if resetPassword && len(password) == 0 { + password = seclib.RandomPassword(12) + } + + guestInfo, err := guest.DeployFs(guestfs.NewDeployInfo( + publicKey, deploys, password, deployParams.IsInit, false, + options.HostOptions.LinuxDefaultRootUser, options.HostOptions.WindowsDefaultAdminUser)) + if err != nil { + log.Errorf("Deploy guest fs error: %s", err) + return nil, err + } else { + return guestInfo, nil + } + } else { + return nil, fmt.Errorf("Guest %s not found", deployParams.Sid) + } +} + +// delay cpuset balance +func (m *SGuestManager) CpusetBalance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + m.cpusetBalance() + return nil, nil +} + +func (m *SGuestManager) Status(sid string) string { + if guest, ok := m.Servers[sid]; ok { + if guest.IsMaster() && !guest.IsMirrorJobSucc() { + return "block_stream" + } + if guest.IsRunning() { + return "running" + } else if guest.IsSuspend() { + return "suspend" + } else { + return "stopped" + } + } else { + return "notfound" + } +} + +func (m *SGuestManager) Delete(sid string) (*SKVMGuestInstance, error) { + if guest, ok := m.Servers[sid]; ok { + delete(m.Servers, sid) + // 这里应该不需要append到deleted servers + // 据观察 deleted servers 目的是为了给ofp_delegate使用,ofp已经不用了 + return guest, nil + } else { + return nil, httperrors.NewNotFoundError("Not found") + } +} + +func (m *SGuestManager) GuestStart(ctx context.Context, sid string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if guest, ok := m.Servers[sid]; ok { + if desc, err := body.Get("desc"); err == nil { + guest.SaveDesc(desc) + } + if guest.IsStopped() { + params, _ := body.Get("params") + guest.StartGuest(ctx, params) + res := jsonutils.NewDict() + res.Set("vnc_port", jsonutils.NewInt(0)) + return res, nil + } else { + vncPort := guest.GetVncPort() + if vncPort > 0 { + res := jsonutils.NewDict() + res.Set("vnc_port", jsonutils.NewInt(int64(vncPort))) + res.Set("is_running", jsonutils.JSONTrue) + return res, nil + } else { + return nil, httperrors.NewBadRequestError("Seems started, but no VNC info") + } + } + } else { + return nil, httperrors.NewNotFoundError("Not found") + } +} + +func (m *SGuestManager) GuestStop(ctx context.Context, sid string, timeout int64) error { + if guest, ok := m.Servers[sid]; ok { + hostutils.DelayTaskWithoutReqctx(ctx, guest.ExecStopTask, timeout) + return nil + } else { + return httperrors.NewNotFoundError("Guest %s not found", sid) + } +} + +func (m *SGuestManager) GuestSync(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + syncParams, ok := params.(*SBaseParms) + if !ok { + return nil, hostutils.ParamsError + } + guest := m.Servers[syncParams.Sid] + if syncParams.Body.Contains("desc") { + desc, _ := syncParams.Body.Get("desc") + fwOnly := jsonutils.QueryBoolean(syncParams.Body, "fw_only", false) + return guest.SyncConfig(ctx, desc, fwOnly) + } + return nil, nil +} + +func (m *SGuestManager) GuestSuspend(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + sid, ok := params.(string) + if !ok { + return nil, hostutils.ParamsError + } + guest := m.Servers[sid] + guest.ExecSuspendTask(ctx) + return nil, nil +} + +func (m *SGuestManager) SrcPrepareMigrate(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + migParams, ok := params.(*SSrcPrepareMigrate) + if !ok { + return nil, hostutils.ParamsError + } + guest := m.Servers[migParams.Sid] + disksPrepare, err := guest.PrepareMigrate(migParams.LiveMigrate) + if err != nil { + return nil, err + } + if disksPrepare.Length() > 0 { + ret := jsonutils.NewDict() + ret.Set("disks_back", disksPrepare) + return ret, nil + } + return nil, nil +} + +func (m *SGuestManager) DestPrepareMigrate(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + migParams, ok := params.(*SDestPrepareMigrate) + if !ok { + return nil, hostutils.ParamsError + } + + guest := m.Servers[migParams.Sid] + if err := guest.CreateFromDesc(migParams.Desc); err != nil { + return nil, err + } + + if len(migParams.TargetStorageId) > 0 { + iStorage := storageman.GetManager().GetStorage(migParams.TargetStorageId) + if iStorage == nil { + return nil, fmt.Errorf("Target storage %s not found", migParams.TargetStorageId) + } + + // 可能可以不用? + // guest.CreateFromUrl(ctx, migParams.ServerUrl, migParams.Desc) + + disks, _ := migParams.Desc.GetArray("disks") + for i, diskinfo := range disks { + var ( + diskId, _ = diskinfo.GetString("disk_id") + snapshots, _ = migParams.SrcSnapshots.GetArray(diskId) + disk = iStorage.CreateDisk(diskId) + ) + + if disk == nil { + return nil, fmt.Errorf( + "Storage %s create disk %s failed", iStorage.GetId(), diskId) + } + + // prepare disk snapshot dir + if len(snapshots) > 0 && !fileutils2.Exists(disk.GetSnapshotDir()) { + _, err := procutils.NewCommand("mkdir", "-p", disk.GetSnapshotDir()).Run() + if err != nil { + return nil, err + } + } + + // create snapshots form remote url + diskStorageId, _ := diskinfo.GetString("storage_id") + for _, snapshotId := range snapshots { + snapId, _ := snapshotId.GetString() + + snapshotUrl := fmt.Sprintf("%s/%s/%s/%s", + migParams.SnapshotsUri, diskStorageId, diskId, snapId) + snapshotPath := path.Join(disk.GetSnapshotDir(), snapId) + log.Infof("Disk %s snapshot %s url: %s", diskId, snapId, snapshotUrl) + iStorage.CreateSnapshotFormUrl(ctx, snapshotUrl, diskId, snapshotPath) + } + + if migParams.LiveMigrate { + // create local disk + backingFile, _ := migParams.DisksBackingFile.GetString(diskId) + size, _ := diskinfo.Int("size") + _, err := disk.CreateRaw(ctx, int(size), "qcow2", "", false, "", backingFile) + if err != nil { + log.Errorln(err) + return nil, err + } + } else { + // download disk form remote url + diskUrl := fmt.Sprintf("%s/%s/%s", migParams.DisksUri, diskStorageId, diskId) + if err := disk.CreateFromUrl(ctx, diskUrl); err != nil { + log.Errorln(err) + return nil, err + } + } + diskDesc, _ := disks[i].(*jsonutils.JSONDict) + diskDesc.Set("path", jsonutils.NewString(disk.GetPath())) + } + + // 可能可以不要 + if err := guest.SaveDesc(migParams.Desc); err != nil { + log.Errorln(err) + return nil, err + } + } + + if migParams.LiveMigrate { + startParams := jsonutils.NewDict() + startParams.Set("qemu_version", jsonutils.NewString(migParams.QemuVersion)) + startParams.Set("need_migrate", jsonutils.JSONTrue) + hostutils.DelayTaskWithoutReqctx(ctx, guest.asyncScriptStart, startParams) + } + + return nil, nil +} + +func (m *SGuestManager) LiveMigrate(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + migParams, ok := params.(*SLiveMigrate) + if !ok { + return nil, hostutils.ParamsError + } + + guest := m.Servers[migParams.Sid] + task := NewGuestLiveMigrateTask(ctx, guest, migParams) + task.Start() + return nil, nil +} + +func (m *SGuestManager) CanMigrate(sid string) bool { + m.ServersLock.Lock() + defer m.ServersLock.Unlock() + + if _, ok := m.Servers[sid]; ok { + log.Infof("Guest %s exists", sid) + return false + } + + guest := NewKVMGuestInstance(sid, m) + m.Servers[sid] = guest + return true +} + +func (m *SGuestManager) GetFreePortByBase(basePort int) int { + var port = 1 + for { + if netutils2.IsTcpPortUsed("0.0.0.0", basePort+port) || + netutils2.IsTcpPortUsed("127.0.0.1", basePort+port) { + port += 1 + } else { + return basePort + port + } + } +} + +func (m *SGuestManager) GetFreeVncPort() int { + vncPorts := make(map[int]struct{}, 0) + for _, guest := range m.Servers { + inUsePort := guest.GetVncPort() + if inUsePort > 0 { + vncPorts[inUsePort] = struct{}{} + } + } + var port = 1 + for { + if _, ok := vncPorts[port]; !ok && !netutils2.IsTcpPortUsed("0.0.0.0", VNC_PORT_BASE+port) && + !netutils2.IsTcpPortUsed("127.0.0.1", MONITOR_PORT_BASE+port) { + break + } else { + port += 1 + } + } + return port +} + +func (m *SGuestManager) ReloadDiskSnapshot( + ctx context.Context, params interface{}, +) (jsonutils.JSONObject, error) { + reloadParams, ok := params.(*SReloadDisk) + if !ok { + return nil, hostutils.ParamsError + } + guest := guestManger.Servers[reloadParams.Sid] + return guest.ExecReloadDiskTask(ctx, reloadParams.Disk) +} + +func (m *SGuestManager) DoSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + snapshotParams, ok := params.(*SDiskSnapshot) + if !ok { + return nil, hostutils.ParamsError + } + guest := guestManger.Servers[snapshotParams.Sid] + return guest.ExecDiskSnapshotTask(ctx, snapshotParams.Disk, snapshotParams.SnapshotId) +} + +func (m *SGuestManager) DeleteSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + delParams, ok := params.(*SDeleteDiskSnapshot) + if !ok { + return nil, hostutils.ParamsError + } + + if len(delParams.ConvertSnapshot) > 0 { + guest := guestManger.Servers[delParams.Sid] + return guest.ExecDeleteSnapshotTask(ctx, delParams.Disk, delParams.DeleteSnapshot, + delParams.ConvertSnapshot, delParams.PendingDelete) + } else { + res := jsonutils.NewDict() + res.Set("deleted", jsonutils.JSONTrue) + return res, delParams.Disk.DeleteSnapshot(delParams.DeleteSnapshot, "", false) + } +} + +func (m *SGuestManager) Resume(ctx context.Context, sid string, isLiveMigrate bool) (jsonutils.JSONObject, error) { + guest := guestManger.Servers[sid] + resumeTask := NewGuestResumeTask(ctx, guest) + if isLiveMigrate { + guest.StartPresendArp() + } + resumeTask.Start() + return nil, nil +} + +func (m *SGuestManager) OnlineResizeDisk(ctx context.Context, sid string, diskId string, sizeMb int64) (jsonutils.JSONObject, error) { + guest, ok := guestManger.Servers[sid] + if !ok { + return nil, httperrors.NewNotFoundError("guest %s not found", sid) + } + if guest.IsRunning() { + guest.onlineResizeDisk(ctx, diskId, sizeMb) + return nil, nil + } else { + return nil, httperrors.NewInvalidStatusError("guest is not runnign") + } +} + +// func (m *SGuestManager) StartNbdServer(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { +// sid, ok := params.(string) +// if !ok { +// return nil, hostutils.ParamsError +// } +// guest := guestManger.Servers[sid] +// port := m.GetFreePortByBase(BUILT_IN_NBD_SERVER_PORT_BASE) + +// } + +func (m *SGuestManager) StartDriveMirror(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + mirrorParams, ok := params.(*SDriverMirror) + if !ok { + return nil, hostutils.ParamsError + } + guest := guestManger.Servers[mirrorParams.Sid] + task := NewDriveMirrorTask(ctx, guest, mirrorParams.NbdServerUri, "top", nil) + task.Start() + return nil, nil +} + +func (m *SGuestManager) ExitGuestCleanup() { + for _, guest := range m.Servers { + guest.ExitCleanup(false) + } + + cgrouputils.CgroupCleanAll() +} + +func (m *SGuestManager) GetHost() hostutils.IHost { + return m.host +} + +var guestManger *SGuestManager + +func Stop() { + guestManger.ExitGuestCleanup() +} + +func Init(host hostutils.IHost, serversPath string) { + if guestManger == nil { + guestManger = NewGuestManager(host, serversPath) + } +} + +func GetGuestManager() *SGuestManager { + return guestManger +} diff --git a/pkg/hostman/guestman/guesttasks.go b/pkg/hostman/guestman/guesttasks.go new file mode 100644 index 0000000000..2ca49c9734 --- /dev/null +++ b/pkg/hostman/guestman/guesttasks.go @@ -0,0 +1,1002 @@ +package guestman + +import ( + "context" + "fmt" + "os" + "path" + "regexp" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appctx" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemuimg" + "yunion.io/x/onecloud/pkg/util/timeutils2" +) + +type IGuestTasks interface { + Start(func(...error)) +} + +/** + * GuestStopTask +**/ + +type SGuestStopTask struct { + *SKVMGuestInstance + ctx context.Context + timeout int64 + startPowerdown time.Time +} + +func NewGuestStopTask(guest *SKVMGuestInstance, ctx context.Context, timeout int64) *SGuestStopTask { + return &SGuestStopTask{ + SKVMGuestInstance: guest, + ctx: ctx, + timeout: timeout, + startPowerdown: time.Time{}, + } +} + +func (s *SGuestStopTask) Start() { + if s.IsRunning() && s.IsMonitorAlive() { + s.Monitor.SimpleCommand("system_powerdown", s.onPowerdownGuest) + } else { + s.checkGuestRunning() + } +} + +func (s *SGuestStopTask) onPowerdownGuest(results string) { + s.ExitCleanup(true) + s.startPowerdown = time.Now() + s.checkGuestRunning() +} + +func (s *SGuestStopTask) checkGuestRunning() { + if !s.IsRunning() || time.Now().Sub(s.startPowerdown) > time.Duration(s.timeout)*time.Second { + s.Stop() // force stop + hostutils.TaskComplete(s.ctx, nil) + } else { + s.CheckGuestRunningLater() + } +} + +func (s *SGuestStopTask) CheckGuestRunningLater() { + time.Sleep(time.Second * 1) + s.checkGuestRunning() +} + +/** + * GuestSyncConfigTaskExecutor +**/ + +type SGuestSyncConfigTaskExecutor struct { + ctx context.Context + guest *SKVMGuestInstance + tasks []IGuestTasks + + errors []error + callback func([]error) +} + +func NewGuestSyncConfigTaskExecutor(ctx context.Context, guest *SKVMGuestInstance, tasks []IGuestTasks, callback func([]error)) *SGuestSyncConfigTaskExecutor { + return &SGuestSyncConfigTaskExecutor{ctx, guest, tasks, make([]error, 0), callback} +} + +func (t *SGuestSyncConfigTaskExecutor) Start(delay int) { + timeutils2.AddTimeout(1*time.Second, t.runNextTask) +} + +func (t *SGuestSyncConfigTaskExecutor) runNextTask() { + if len(t.tasks) > 0 { + task := t.tasks[len(t.tasks)-1] + t.tasks = t.tasks[:len(t.tasks)-1] + task.Start(t.runNextTaskCallback) + } else { + t.doCallback() + } +} + +func (t *SGuestSyncConfigTaskExecutor) doCallback() { + if t.callback != nil { + t.callback(t.errors) + t.callback = nil + } +} + +func (t *SGuestSyncConfigTaskExecutor) runNextTaskCallback(err ...error) { + if err != nil { + t.errors = append(t.errors, err...) + } + t.runNextTask() +} + +/** + * GuestDiskSyncTask +**/ + +type SGuestDiskSyncTask struct { + guest *SKVMGuestInstance + delDisks []jsonutils.JSONObject + addDisks []jsonutils.JSONObject + cdrom *string + + callback func(...error) +} + +func NewGuestDiskSyncTask(guest *SKVMGuestInstance, delDisks, addDisks []jsonutils.JSONObject, cdrom *string) *SGuestDiskSyncTask { + return &SGuestDiskSyncTask{guest, delDisks, addDisks, cdrom, nil} +} + +func (d *SGuestDiskSyncTask) Start(callback func(...error)) { + d.callback = callback + d.syncDisksConf() +} + +func (d *SGuestDiskSyncTask) syncDisksConf() { + if len(d.delDisks) > 0 { + disk := d.delDisks[len(d.delDisks)-1] + d.delDisks = d.delDisks[:len(d.delDisks)-1] + d.removeDisk(disk) + return + } + if len(d.addDisks) > 0 { + disk := d.addDisks[len(d.addDisks)-1] + d.addDisks = d.addDisks[:len(d.addDisks)-1] + d.addDisk(disk) + return + } + if d.cdrom != nil { + d.changeCdrom() + return + } + d.callback() +} + +func (d *SGuestDiskSyncTask) changeCdrom() { + d.guest.Monitor.GetBlocks(d.onGetBlockInfo) +} + +func (d *SGuestDiskSyncTask) onGetBlockInfo(results *jsonutils.JSONArray) { + var cdName string + for _, r := range results.Value() { + device, _ := r.GetString("device") + if regexp.MustCompile(`^ide\d+-cd\d+$`).MatchString(device) { + cdName = device + break + } + } + if len(cdName) > 0 { + d.changeCdromContent(cdName) + } +} + +func (d *SGuestDiskSyncTask) changeCdromContent(cdName string) { + if *d.cdrom == "" { + d.guest.Monitor.EjectCdrom(cdName, d.OnChangeCdromContentSucc) + } else { + d.guest.Monitor.ChangeCdrom(cdName, *d.cdrom, d.OnChangeCdromContentSucc) + } +} + +func (d *SGuestDiskSyncTask) OnChangeCdromContentSucc(results string) { + d.cdrom = nil + d.syncDisksConf() +} + +func (d *SGuestDiskSyncTask) removeDisk(disk jsonutils.JSONObject) { + index, _ := disk.Int("index") + devId := fmt.Sprintf("drive_%d", index) + d.guest.Monitor.DriveDel(devId, + func(results string) { d.onRemoveDriveSucc(devId, results) }) +} + +func (d *SGuestDiskSyncTask) onRemoveDriveSucc(devId, results string) { + d.guest.Monitor.DeviceDel(devId, d.onRemoveDiskSucc) +} + +func (d *SGuestDiskSyncTask) onRemoveDiskSucc(results string) { + d.syncDisksConf() +} + +func (d *SGuestDiskSyncTask) addDisk(disk jsonutils.JSONObject) { + diskPath, _ := disk.GetString("path") + iDisk := storageman.GetManager().GetDiskByPath(diskPath) + if iDisk == nil { + d.syncDisksConf() + return + } + + var ( + diskIndex, _ = disk.Int("index") + aio, _ = disk.GetString("aio_mode") + diskDirver, _ = disk.GetString("driver") + cacheMode, _ = disk.GetString("cache_mode") + ) + + var params = map[string]string{ + "file": iDisk.GetPath(), + "if": "none", + "id": fmt.Sprintf("drive_%d", diskIndex), + "cache": cacheMode, + "aio": aio, + } + + var bus string + switch diskDirver { + case DISK_DRIVER_SCSI: + bus = "scsi.0" + case DISK_DRIVER_VIRTIO: + bus = d.guest.GetPciBus() + case DISK_DRIVER_IDE: + bus = fmt.Sprintf("ide.%d", diskIndex/2) + case DISK_DRIVER_SATA: + bus = fmt.Sprintf("ide.%d", diskIndex) + } + d.guest.Monitor.DriveAdd(bus, params, func(result string) { d.onAddDiskSucc(disk, result) }) +} + +func (d *SGuestDiskSyncTask) onAddDiskSucc(disk jsonutils.JSONObject, results string) { + var ( + diskIndex, _ = disk.Int("index") + diskDirver, _ = disk.GetString("driver") + dev = d.guest.GetDiskDeviceModel(diskDirver) + ) + + var params = map[string]interface{}{ + "drive": fmt.Sprintf("drive_%d", diskIndex), + "id": fmt.Sprintf("drive_%d", diskIndex), + } + + if diskDirver == DISK_DRIVER_VIRTIO { + params["addr"] = fmt.Sprintf("0x%x", d.guest.GetDiskAddr(int(diskIndex))) + } else if DISK_DRIVER_IDE == diskDirver { + params["unit"] = diskIndex % 2 + } + d.guest.Monitor.DeviceAdd(dev, params, d.onAddDeviceSucc) +} + +func (d *SGuestDiskSyncTask) onAddDeviceSucc(results string) { + d.syncDisksConf() +} + +/** + * GuestNetworkSyncTask +**/ + +type SGuestNetworkSyncTask struct { + guest *SKVMGuestInstance + delNics []jsonutils.JSONObject + addNics []jsonutils.JSONObject + errors []error + + callback func(...error) +} + +func (n *SGuestNetworkSyncTask) Start(callback func(...error)) { + n.callback = callback + n.syncNetworkConf() +} + +func (n *SGuestNetworkSyncTask) syncNetworkConf() { + if len(n.delNics) > 0 { + nic := n.delNics[len(n.delNics)-1] + n.delNics = n.delNics[:len(n.delNics)-1] + n.removeNic(nic) + return + } else if len(n.addNics) > 0 { + nic := n.addNics[len(n.addNics)-1] + n.addNics = n.addNics[:len(n.addNics)-1] + n.addNic(nic) + return + } else { + n.callback() + } +} + +func (n *SGuestNetworkSyncTask) removeNic(nic jsonutils.JSONObject) { + // pass not implement +} + +func (n *SGuestNetworkSyncTask) addNic(nic jsonutils.JSONObject) { + // pass not implement +} + +func NewGuestNetworkSyncTask(guest *SKVMGuestInstance, delNics, addNics []jsonutils.JSONObject) *SGuestNetworkSyncTask { + return &SGuestNetworkSyncTask{guest, delNics, addNics, make([]error, 0), nil} +} + +/** + * GuestLiveMigrateTask +**/ + +type SGuestLiveMigrateTask struct { + *SKVMGuestInstance + + ctx context.Context + params *SLiveMigrate + + c chan struct{} +} + +func NewGuestLiveMigrateTask( + ctx context.Context, guest *SKVMGuestInstance, params *SLiveMigrate, +) *SGuestLiveMigrateTask { + return &SGuestLiveMigrateTask{SKVMGuestInstance: guest, ctx: ctx, params: params} +} + +func (s *SGuestLiveMigrateTask) Start() { + s.Monitor.MigrateSetCapability("zero-blocks", "on", s.startMigrate) +} + +func (s *SGuestLiveMigrateTask) startMigrate(string) { + var copyIncremental = false + if s.params.IsLocal { + copyIncremental = true + } + s.Monitor.Migrate(fmt.Sprintf("tcp:%s:%d", s.params.DestIp, s.params.DestPort), + copyIncremental, false, s.startMigrateStatusCheck) +} + +func (s *SGuestLiveMigrateTask) startMigrateStatusCheck(string) { + s.c = make(chan struct{}) + for { + select { + case <-s.c: // on c close + break + case <-time.After(time.Second * 1): + s.Monitor.GetMigrateStatus(s.onGetMigrateStatus) + } + } +} + +func (s *SGuestLiveMigrateTask) onGetMigrateStatus(status string) { + if status == "completed" { + close(s.c) + hostutils.TaskComplete(s.ctx, nil) + } else if status == "failed" { + close(s.c) + hostutils.TaskFailed(s.ctx, fmt.Sprintf("Query migrate got status: %s", status)) + } +} + +/** + * GuestResumeTask +**/ + +type SGuestResumeTask struct { + *SKVMGuestInstance + + ctx context.Context + startTime time.Time +} + +func NewGuestResumeTask(ctx context.Context, s *SKVMGuestInstance) *SGuestResumeTask { + return &SGuestResumeTask{ + SKVMGuestInstance: s, + ctx: ctx, + } +} + +func (s *SGuestResumeTask) Start() { + s.startTime = time.Now() + s.confirmRunning() +} + +func (s *SGuestResumeTask) Stop() { + // TODO + // stop stream disk +} + +func (s *SGuestResumeTask) confirmRunning() { + s.Monitor.QueryStatus(s.onConfirmRunning) +} + +func (s *SGuestResumeTask) onConfirmRunning(status string) { + if status == "running" || status == "paused (suspended)" || status == "paused (perlaunch)" { + s.onStartRunning() + } else if strings.Contains(status, "paused") { + s.Monitor.GetBlocks(s.onGetBlockInfo) + } else if strings.Contains(status, "error") { + s.taskFailed(status) + } else { + if time.Now().Sub(s.startTime) >= time.Second*60 { + s.taskFailed("Timeout") + } else { + time.Sleep(time.Second * 1) + s.confirmRunning() + } + } +} + +func (s *SGuestResumeTask) taskFailed(reason string) { + log.Infof("Start guest %s failed: %s", s.Id, reason) + s.ForceStop() + if s.ctx != nil && len(appctx.AppContextTaskId(s.ctx)) > 0 { + hostutils.TaskFailed(s.ctx, reason) + } else { + s.SyncStatus() + } +} + +func (s *SGuestResumeTask) onGetBlockInfo(results *jsonutils.JSONArray) { + // for _, drv := range results.GetArray() { + // // encryption not work + // } + time.Sleep(time.Second * 1) + s.resumeGuest() +} + +func (s *SGuestResumeTask) resumeGuest() { + s.startTime = time.Now() + s.Monitor.SimpleCommand("cont", s.onResumeSucc) +} + +func (s *SGuestResumeTask) onResumeSucc(res string) { + s.confirmRunning() +} + +func (s *SGuestResumeTask) onStartRunning() { + // s.removeStatefile() XXX 可能不用了,先注释了 + if s.ctx != nil && len(appctx.AppContextTaskId(s.ctx)) > 0 { + hostutils.TaskComplete(s.ctx, nil) + } + if options.HostOptions.SetVncPassword { + s.SetVncPassword() + } + s.syncMetadata() + s.SyncStatus() + timeutils2.AddTimeout(time.Second*5, s.SetCgroup) + disksIdx := s.GetNeedMergeBackingFileDiskIndexs() + if len(disksIdx) > 0 { + timeutils2.AddTimeout(time.Second*5, func() { s.startStreamDisks(disksIdx) }) + } else if options.HostOptions.AutoMergeBackingTemplate { + timeutils2.AddTimeout( + time.Second*time.Duration(options.HostOptions.AutoMergeDelaySeconds), + func() { s.startStreamDisks(nil) }) + } +} + +func (s *SGuestResumeTask) startStreamDisks(disksIdx []int) { + s.startTime = time.Time{} + s.CleanStartupTask() + if s.IsMonitorAlive() { + s.StreamDisks(s.ctx, func() { s.onStreamComplete(disksIdx) }, disksIdx) + } +} + +func (s *SGuestResumeTask) onStreamComplete(disksIdx []int) { + if len(disksIdx) == 0 { + s.SyncStatus() + } else { + s.streamDisksComplete(s.ctx) + } +} + +func (s *SGuestResumeTask) syncMetadata() { + meta := jsonutils.NewDict() + meta.Set("__qemu_version", jsonutils.NewString(s.GetQemuVersionStr())) + meta.Set("__vnc_port", jsonutils.NewInt(int64(s.GetVncPort()))) + + if len(s.VncPassword) > 0 { + meta.Set("__vnc_password", jsonutils.NewString(s.VncPassword)) + } + + s.SyncMetadata(meta) +} + +func (s *SGuestResumeTask) removeStatefile() { + go s.CleanStatefiles() +} + +/** + * GuestStreamDisksTask +**/ + +type SGuestStreamDisksTask struct { + *SKVMGuestInstance + + ctx context.Context + callback func() + disksIdx []int + + c chan struct{} + streamDevs []string +} + +func NewGuestStreamDisksTask(ctx context.Context, guest *SKVMGuestInstance, callback func(), disksIdx []int) *SGuestStreamDisksTask { + return &SGuestStreamDisksTask{ + SKVMGuestInstance: guest, + ctx: ctx, + callback: callback, + disksIdx: disksIdx, + } +} + +func (s *SGuestStreamDisksTask) Start() { + s.Monitor.GetBlockJobs(s.onInitCheckStreamJobs) +} + +func (s *SGuestStreamDisksTask) onInitCheckStreamJobs(jobs int) { + if jobs > 0 { + log.Warningf("GuestStreamDisksTask: duplicate block streaming???") + s.startWaitBlockStream("") + } else if jobs == 0 { + s.startBlockStreaming() + } +} + +func (s *SGuestStreamDisksTask) startBlockStreaming() { + s.checkBlockDrives() +} + +func (s *SGuestStreamDisksTask) checkBlockDrives() { + s.Monitor.GetBlocks(s.onBlockDrivesSucc) +} + +func (s *SGuestStreamDisksTask) onBlockDrivesSucc(res *jsonutils.JSONArray) { + streamDevs := []string{} + drvs, _ := res.GetArray() + for _, drv := range drvs { + device, err := drv.GetString("device") + if err != nil { + log.Errorln(err) + continue + } + inserted, err := drv.Get("inserted") + if err != nil && inserted.Contains("file") && inserted.Contains("backing_file") { + var stream = false + idx := device[len(device)-1] - '0' + for i := 0; i < len(s.disksIdx); i++ { + if int(idx) == s.disksIdx[i] { + stream = true + } + } + if !stream { + continue + } + streamDevs = append(streamDevs, device) + } + } + s.streamDevs = streamDevs + if len(streamDevs) == 0 { + s.taskComplete() + } else { + s.SyncStatus() + s.startDoBlockStream() + } +} + +func (s *SGuestStreamDisksTask) startDoBlockStream() { + if len(s.streamDevs) > 0 { + dev := s.streamDevs[0] + s.streamDevs = s.streamDevs[1:] + s.Monitor.BlockStream(dev, s.startWaitBlockStream) + } +} + +func (s *SGuestStreamDisksTask) startWaitBlockStream(res string) { + if s.c == nil { + s.c = make(chan struct{}) + for { + select { + case <-s.c: + s.c = nil + return + case <-time.After(time.Second * 1): + s.Monitor.GetBlockJobs(s.checkStreamJobs) + } + } + } +} + +func (s *SGuestStreamDisksTask) checkStreamJobs(jobs int) { + if jobs == 0 { + if len(s.streamDevs) == 0 { + close(s.c) + s.taskComplete() + } else { + close(s.c) + } + } +} + +func (s *SGuestStreamDisksTask) taskComplete() { + s.SyncStatus() + + // XXX: region disk post-migrate not implement + + // disks, _ := s.Desc.GetArray("disks") + // var needSync = fale + // for i, disk := range disks { + // if disk.Contains("url") && disk.Contains("path") { + // diskId, _ := disk.GetString("disk_id") + // targetStroageId, _ := disk.GetString("target_storage_id") + // params := jsonutils.NewDict() + // params.Set("storage_id", jsonutils.NewString(targetStroageId)) + // modules.Disks.PerformAction(hostutils.GetComputeSession(context.Background()), + // diskId, "post-migrate", params) + // needSync = true + // } + // } + // if needSync { + // s.SaveDesc(s.Desc) + // } + + if s.callback != nil { + s.callback() + } +} + +/** + * GuestReloadDiskTask +**/ + +type SGuestReloadDiskTask struct { + *SKVMGuestInstance + + ctx context.Context + disk storageman.IDisk +} + +func NewGuestReloadDiskTask( + ctx context.Context, s *SKVMGuestInstance, disk storageman.IDisk, +) *SGuestReloadDiskTask { + return &SGuestReloadDiskTask{ + SKVMGuestInstance: s, + ctx: ctx, + disk: disk, + } +} + +func (s *SGuestReloadDiskTask) WaitSnapshotReplaced(callback func()) error { + var retry = 0 + for { + retry += 1 + if retry == 300 { + return fmt.Errorf( + "SnapshotDeleteJob.deleting_disk_snapshot always has %s", s.disk.GetId()) + } + if _, ok := storageman.DELETEING_SNAPSHOTS[s.disk.GetId()]; ok { + time.Sleep(time.Second * 1) + } + } + + callback() + return nil +} + +func (s *SGuestReloadDiskTask) Start() { + s.fetchDisksInfo(s.startReloadDisk) +} + +func (s *SGuestReloadDiskTask) fetchDisksInfo(callback func(string)) { + s.Monitor.GetBlocks(func(res *jsonutils.JSONArray) { s.onGetBlocksSucc(res, callback) }) +} + +func (s *SGuestReloadDiskTask) onGetBlocksSucc(res *jsonutils.JSONArray, callback func(string)) { + var device string + devs, _ := res.GetArray() + for _, d := range devs { + device = s.getDiskOfDrive(d) + if len(device) > 0 { + callback(device) + break + } + } + + if len(device) == 0 { + s.taskFailed("Device not found") + } +} + +func (s *SGuestReloadDiskTask) getDiskOfDrive(d jsonutils.JSONObject) string { + inserted, err := d.Get("inserted") + if err != nil { + return "" + } + file, err := inserted.GetString("file") + if err != nil { + return "" + } + if file == s.disk.GetPath() { + drive, _ := d.GetString("device") + return drive + } + return "" +} + +func (s *SGuestReloadDiskTask) startReloadDisk(device string) { + s.doReloadDisk(device, s.onReloadSucc) +} + +func (s *SGuestReloadDiskTask) doReloadDisk(device string, callback func(string)) { + s.Monitor.SimpleCommand("stop", func(string) { + s.Monitor.ReloadDiskBlkdev(device, s.disk.GetPath(), callback) + }) +} + +func (s *SGuestReloadDiskTask) onReloadSucc(err string) { + if len(err) > 0 { + log.Errorf("monitor new snapshot blkdev error: %s", err) + } + s.Monitor.SimpleCommand("cont", s.onResumeSucc) +} + +func (s *SGuestReloadDiskTask) onResumeSucc(results string) { + log.Infof("guest reload disk task resume succ %s", results) + hostutils.TaskComplete(s.ctx, nil) +} + +func (s *SGuestReloadDiskTask) taskFailed(reason string) { + log.Errorf("SGuestReloadDiskTask error: %s", reason) + hostutils.TaskFailed(s.ctx, reason) +} + +/** + * GuestDiskSnapshotTask +**/ + +type SGuestDiskSnapshotTask struct { + *SGuestReloadDiskTask + + snapshotId string +} + +func NewGuestDiskSnapshotTask( + ctx context.Context, s *SKVMGuestInstance, disk storageman.IDisk, snapshotId string, +) *SGuestDiskSnapshotTask { + return &SGuestDiskSnapshotTask{ + SGuestReloadDiskTask: NewGuestReloadDiskTask(ctx, s, disk), + snapshotId: snapshotId, + } +} + +func (s *SGuestDiskSnapshotTask) Start() { + s.fetchDisksInfo(s.startSnapshot) +} + +func (s *SGuestDiskSnapshotTask) startSnapshot(device string) { + s.doReloadDisk(device, s.onReloadBlkdevSucc) +} + +func (s *SGuestDiskSnapshotTask) onReloadBlkdevSucc(res string) { + var cb = s.onResumeSucc + if len(res) > 0 { + log.Errorf("Monitor reload blkdev error: %s", res) + cb = s.onSnapshotBlkdevFail + } + s.Monitor.SimpleCommand("cont", cb) +} + +func (s *SGuestDiskSnapshotTask) onSnapshotBlkdevFail(string) { + snapshotDir := s.disk.GetSnapshotDir() + snapshotPath := path.Join(snapshotDir, s.snapshotId) + _, err := procutils.NewCommand("rm", "-rf", snapshotPath).Run() + if err != nil { + log.Errorln(err) + } + hostutils.TaskFailed(s.ctx, "Reload blkdev error") +} + +func (s *SGuestDiskSnapshotTask) onResumeSucc(res string) { + log.Infof("guest disk snapshot task resume succ %s", res) + snapshotDir := s.disk.GetSnapshotDir() + snapshotLocation := path.Join(snapshotDir, s.snapshotId) + body := jsonutils.NewDict() + body.Set("location", jsonutils.NewString(snapshotLocation)) + hostutils.TaskComplete(s.ctx, body) +} + +/** + * GuestSnapshotDeleteTask +**/ + +type SGuestSnapshotDeleteTask struct { + *SGuestReloadDiskTask + deleteSnapshot string + convertSnapshot string + pendingDelete bool + + tmpPath string +} + +func NewGuestSnapshotDeleteTask( + ctx context.Context, s *SKVMGuestInstance, disk storageman.IDisk, + deleteSnapshot, convertSnapshot string, pendingDelete bool, +) *SGuestSnapshotDeleteTask { + return &SGuestSnapshotDeleteTask{ + SGuestReloadDiskTask: NewGuestReloadDiskTask(ctx, s, disk), + deleteSnapshot: deleteSnapshot, + convertSnapshot: convertSnapshot, + pendingDelete: pendingDelete, + } +} + +func (s *SGuestSnapshotDeleteTask) Start() { + if err := s.doDiskConvert(); err != nil { + s.taskFailed(err.Error()) + } + s.fetchDisksInfo(s.doReloadDisk) +} + +func (s *SGuestSnapshotDeleteTask) doDiskConvert() error { + snapshotDir := s.disk.GetSnapshotDir() + snapshotPath := path.Join(snapshotDir, s.convertSnapshot) + img, err := qemuimg.NewQemuImage(snapshotPath) + if err != nil { + log.Errorln(err) + return err + } + convertedDisk := snapshotPath + ".tmp" + if err = img.Convert2Qcow2To(convertedDisk, true); err != nil { + log.Errorln(err) + if fileutils2.Exists(convertedDisk) { + os.Remove(convertedDisk) + } + return err + } + + s.tmpPath = snapshotPath + ".swap" + if _, err := procutils.NewCommand("mv", "-f", snapshotPath, s.tmpPath).Run(); err != nil { + log.Errorln(err) + if fileutils2.Exists(s.tmpPath) { + procutils.NewCommand("mv", "-f", s.tmpPath, snapshotPath).Run() + } + return err + } + if _, err := procutils.NewCommand("mv", "-f", convertedDisk, snapshotPath).Run(); err != nil { + log.Errorln(err) + if fileutils2.Exists(s.tmpPath) { + procutils.NewCommand("mv", "-f", s.tmpPath, snapshotPath).Run() + } + return err + } + return nil +} + +func (s *SGuestSnapshotDeleteTask) doReloadDisk(device string) { + s.SGuestReloadDiskTask.doReloadDisk(device, s.onReloadBlkdevSucc) +} + +func (s *SGuestSnapshotDeleteTask) onReloadBlkdevSucc(err string) { + var callback = s.onResumeSucc + if len(err) > 0 { + log.Errorln("Reload blkdev failed: %s", err) + callback = s.onSnapshotBlkdevFail + } + s.Monitor.SimpleCommand("cont", callback) +} + +func (s *SGuestSnapshotDeleteTask) onSnapshotBlkdevFail(res string) { + snapshotPath := path.Join(s.disk.GetSnapshotDir(), s.convertSnapshot) + if _, err := procutils.NewCommand("rm", "-f", s.tmpPath, snapshotPath).Run(); err != nil { + log.Errorln(err) + } + s.taskFailed("Reload blkdev failed") +} + +func (s *SGuestSnapshotDeleteTask) onResumeSucc(res string) { + log.Infof("guest do new snapshot task resume succ %s", res) + if len(s.tmpPath) > 0 { + _, err := procutils.NewCommand("rm", "-f", s.tmpPath).Run() + if err != nil { + log.Errorln(err) + } + } + if !s.pendingDelete { + snapshotDir := s.disk.GetSnapshotDir() + procutils.NewCommand("rm", "-f", path.Join(snapshotDir, s.deleteSnapshot)) + } + body := jsonutils.NewDict() + body.Set("deleted", jsonutils.JSONTrue) + hostutils.TaskComplete(s.ctx, body) +} + +/** + * GuestDriveMirrorTask +**/ + +type SDriveMirrorTask struct { + *SKVMGuestInstance + + ctx context.Context + nbdUri string + onSucc func() + syncMode string + index int +} + +func NewDriveMirrorTask( + ctx context.Context, s *SKVMGuestInstance, nbdUri, syncMode string, onSucc func(), +) *SDriveMirrorTask { + return &SDriveMirrorTask{ + SKVMGuestInstance: s, + ctx: ctx, + nbdUri: nbdUri, + syncMode: syncMode, + onSucc: onSucc, + } +} + +func (s *SDriveMirrorTask) Start() { + s.startMirror("") +} + +func (s *SDriveMirrorTask) startMirror(res string) { + log.Infof("drive mirror results:%s", res) + if len(res) > 0 { + hostutils.TaskFailed(s.ctx, res) + return + } + disks, _ := s.Desc.GetArray("disks") + if s.index < len(disks) { + if s.index >= 1 { // data disk + s.syncMode = "none" + } + target := fmt.Sprintf("%s:exportname=drive_%d", s.nbdUri, s.index) + s.Monitor.DriveMirror(s.startMirror, fmt.Sprintf("drive_%d", s.index), + target, s.syncMode, true) + s.index += 1 + } else { + if s.onSucc != nil { + s.onSucc() + } else { + hostutils.TaskComplete(s.ctx, nil) + } + } +} + +/** + * GuestOnlineResizeDiskTask +**/ + +type SGuestOnlineResizeDiskTask struct { + *SKVMGuestInstance + + ctx context.Context + diskId string + sizeMB int64 +} + +func NewGuestOnlineResizeDiskTask( + ctx context.Context, s *SKVMGuestInstance, diskId string, sizeMB int64, +) *SGuestOnlineResizeDiskTask { + return &SGuestOnlineResizeDiskTask{ + SKVMGuestInstance: s, + ctx: ctx, + diskId: diskId, + sizeMB: sizeMB, + } +} + +func (task *SGuestOnlineResizeDiskTask) Start() { + task.Monitor.GetBlocks(task.OnGetBlocksSucc) +} + +func (task *SGuestOnlineResizeDiskTask) OnGetBlocksSucc(results *jsonutils.JSONArray) { + for i := 0; i < results.Size(); i += 1 { + result, _ := results.GetAt(i) + fileStr, _ := result.GetString("inserted", "file") + if len(fileStr) > 0 && strings.HasSuffix(fileStr, task.diskId) { + driveName, _ := result.GetString("device") + task.Monitor.ResizeDisk(driveName, task.sizeMB, task.OnResizeSucc) + return + } + } + hostutils.TaskFailed(task.ctx, fmt.Sprintf("disk %s not found on this guest", task.diskId)) +} + +func (task *SGuestOnlineResizeDiskTask) OnResizeSucc(result string) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewInt(task.sizeMB), "disk_size") + hostutils.TaskComplete(task.ctx, params) +} diff --git a/pkg/hostman/guestman/qemu-kvm.go b/pkg/hostman/guestman/qemu-kvm.go new file mode 100644 index 0000000000..ab8467b98a --- /dev/null +++ b/pkg/hostman/guestman/qemu-kvm.go @@ -0,0 +1,1176 @@ +package guestman + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/seclib" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/appctx" + "yunion.io/x/onecloud/pkg/cloudcommon/storagetypes" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/monitor" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/cgrouputils" + "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/timeutils2" + "yunion.io/x/onecloud/pkg/util/version" +) + +const ( + STATE_FILE_PREFIX = "STATEFILE" + MONITOR_PORT_BASE = 55900 + LIVE_MIGRATE_PORT_BASE = 4396 + BUILT_IN_NBD_SERVER_PORT_BASE = 7777 + MAX_TRY = 3 +) + +type SKVMGuestInstance struct { + Id string + + cgroupPid int + QemuVersion string + VncPassword string + + Desc *jsonutils.JSONDict + Monitor monitor.Monitor + manager *SGuestManager + + startupTask *SGuestResumeTask + mirrorJobSuccCount *int +} + +func NewKVMGuestInstance(id string, manager *SGuestManager) *SKVMGuestInstance { + return &SKVMGuestInstance{ + Id: id, + manager: manager, + } +} + +func (s *SKVMGuestInstance) GetId() string { + id, _ := s.Desc.GetString("uuid") + return id +} + +func (s *SKVMGuestInstance) GetName() string { + id, _ := s.Desc.GetString("uuid") + name, _ := s.Desc.GetString("name") + return fmt.Sprintf("%s(%s)", name, id) +} + +func (s *SKVMGuestInstance) getStateFilePathRootPrefix() string { + return path.Join(s.HomeDir(), STATE_FILE_PREFIX) +} + +func (s *SKVMGuestInstance) IsLoaded() bool { + return s.Desc != nil +} + +func (s *SKVMGuestInstance) HomeDir() string { + return path.Join(s.manager.ServersPath, s.Id) +} + +func (s *SKVMGuestInstance) PrepareDir() error { + _, err := procutils.NewCommand("mkdir", "-p", s.HomeDir()).Run() + return err +} + +func (s *SKVMGuestInstance) GetPidFilePath() string { + return path.Join(s.HomeDir(), "pid") +} + +func (s *SKVMGuestInstance) GetVncFilePath() string { + return path.Join(s.HomeDir(), "vnc") +} + +func (s *SKVMGuestInstance) GetPid() int { + return s.getPid(s.GetPidFilePath(), s.Id) +} + +func (s *SKVMGuestInstance) getPid(pidFile, uuid string) int { + if !fileutils2.Exists(pidFile) { + return -1 + } + pidStr, err := fileutils2.FileGetContents(pidFile) + if err != nil { + log.Errorln(err) + return -2 + } + pidStr = strings.TrimSpace(pidStr) + pid := s.findPid(strings.Split(pidStr, "\n"), uuid) + if len(pid) > 0 && regutils.MatchInteger(pid) { + v, _ := strconv.ParseInt(pid, 10, 0) + return int(v) + } + return -2 +} + +func (s *SKVMGuestInstance) findPid(pids []string, uuid string) string { + if len(pids) == 0 { + return "" + } + for _, pid := range pids { + pid := strings.TrimSpace(pid) + if s.isSelfQemuPid(pid, uuid) { + return pid + } + } + return "" +} + +func (s *SKVMGuestInstance) isSelfQemuPid(pid, uuid string) bool { + if len(pid) == 0 { + return false + } + cmdlineFile := fmt.Sprintf("/proc/%s/cmdline", pid) + fi, err := os.Stat(cmdlineFile) + if err != nil { + return false + } + if !fi.Mode().IsRegular() { + return false + } + cmdline, err := ioutil.ReadFile(cmdlineFile) + if err != nil { + log.Warningf("IsSelfQemuPid Read File %s error %s", cmdlineFile, err) + return false + } + return bytes.Index(cmdline, []byte("qemu-system")) >= 0 && + bytes.Index(cmdline, []byte(uuid)) >= 0 +} + +func (s *SKVMGuestInstance) GetDescFilePath() string { + return path.Join(s.HomeDir(), "desc") +} + +func (s *SKVMGuestInstance) LoadDesc() error { + descPath := s.GetDescFilePath() + descStr, err := ioutil.ReadFile(descPath) + if err != nil { + return err + } + desc, err := jsonutils.Parse(descStr) + if err != nil { + return err + } + dDesc, ok := desc.(*jsonutils.JSONDict) + if !ok { + return fmt.Errorf("Load and parse desc error") + } + s.Desc = dDesc + return nil +} + +func (s *SKVMGuestInstance) IsDirtyShotdown() bool { + return s.GetPid() == -2 +} + +func (s *SKVMGuestInstance) DirtyServerRequestStart() { + hostId, _ := s.Desc.GetString("host_id") + var body = jsonutils.NewDict() + body.Set("guest_id", jsonutils.NewString(s.Id)) + body.Set("host_id", jsonutils.NewString(hostId)) + _, err := modules.Servers.PerformClassAction( + hostutils.GetComputeSession(context.Background()), "dirty-server-start", body) + if err != nil { + log.Errorf("Dirty server request start error: %s", err) + } +} + +// Delay Process +func (s *SKVMGuestInstance) asyncScriptStart(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + data, ok := params.(*jsonutils.JSONDict) + if !ok { + return nil, hostutils.ParamsError + } + + hostbridge.CleanDeletedPorts() + + time.Sleep(100 * time.Millisecond) + var isStarted, tried = false, 0 + var err error + for !isStarted && tried < MAX_TRY { + tried += 1 + + vncPort := s.manager.GetFreeVncPort() + if err = s.saveVncPort(vncPort); err != nil { + goto finally + } else { + data.Set("vnc_port", jsonutils.NewInt(int64(vncPort))) + } + + if err = s.saveScripts(data); err != nil { + goto finally + } else { + err = s.scriptStart() + if err == nil { + isStarted = true + } + } + + finally: + if !isStarted { + log.Errorf("Start VM failed: %s", err) + time.Sleep(time.Duration(1< 0 +} + +func (s *SKVMGuestInstance) IsStopped() bool { + return !s.IsRunning() +} + +func (s *SKVMGuestInstance) IsSuspend() bool { + if !s.IsRunning() && len(s.ListStateFilePaths()) > 0 { + return true + } + return false +} + +func (s *SKVMGuestInstance) IsMonitorAlive() bool { + return s.Monitor != nil && s.Monitor.IsConnected() +} + +// func (s *SKVMGuestInstance) ListStateFilePaths() []string { +// files, err := ioutil.ReadDir(s.HomeDir()) +// if err == nil { +// var ret = make([]string, 0) +// for i := 0; i < len(files); i++ { +// if strings.HasPrefix(files[i].Name(), STATE_FILE_PREFIX) { +// ret = append(ret, files[i].Name()) +// } +// } +// return ret +// } +// return nil +// } + +func (s *SKVMGuestInstance) StartMonitor(ctx context.Context) { + timeutils2.AddTimeout(100*time.Millisecond, func() { s.delayStartMonitor(ctx) }) +} + +func (s *SKVMGuestInstance) delayStartMonitor(ctx context.Context) { + if options.HostOptions.EnableQmpMonitor && s.GetQmpMonitorPort(-1) > 0 { + s.Monitor = monitor.NewQmpMonitor( + s.onMonitorDisConnect, // on monitor disconnect + func(err error) { s.onMonitorTimeout(ctx, err) }, // on monitor timeout + func() { s.onMonitorConnected(ctx) }, // on monitor connected + s.onReceiveQMPEvent, // on reveive qmp event + ) + s.Monitor.Connect("127.0.0.1", s.GetQmpMonitorPort(-1)) + } +} + +func (s *SKVMGuestInstance) onReceiveQMPEvent(event *monitor.Event) { + if event.Event == "BLOCK_JOB_READY" && s.IsMaster() { + if itype, ok := event.Data["type"]; ok { + stype, _ := itype.(string) + if stype == "mirror" { + if s.mirrorJobSuccCount != nil { + *s.mirrorJobSuccCount += 1 + } else { + s.mirrorJobSuccCount = new(int) + *s.mirrorJobSuccCount = 1 + } + if *s.mirrorJobSuccCount == s.DiskCount() { + hostutils.UpdateServerStatus(context.Background(), s.GetId(), "running") + } + } + } + } else if event.Event == "BLOCK_JOB_ERROR" && s.IsMaster() { + modules.Servers.PerformAction(hostutils.GetComputeSession(context.Background()), s.GetId(), "mirror-job-failed", nil) + } +} + +func (s *SKVMGuestInstance) onMonitorConnected(ctx context.Context) { + log.Infof("Monitor connected ...") + s.Monitor.GetVersion(func(v string) { + s.onGetQemuVersion(ctx, v) + }) +} + +func (s *SKVMGuestInstance) onGetQemuVersion(ctx context.Context, version string) { + s.QemuVersion = version + log.Infof("Guest(%s) qemu version %s", s.Id, s.QemuVersion) + if s.Desc.Contains("live_migrate_dest_port") && ctx != nil { + migratePort, _ := s.Desc.Get("live_migrate_dest_port") + body := jsonutils.NewDict() + body.Set("live_migrate_dest_port", migratePort) + hostutils.TaskComplete(ctx, body) + } else if jsonutils.QueryBoolean(s.Desc, "is_slave", false) { + if len(appctx.AppContextTaskId(ctx)) > 0 { + s.startQemuBuiltInNbdServer(ctx) + } + } else if jsonutils.QueryBoolean(s.Desc, "is_master", false) && ctx == nil { + return + } else { + s.DoResumeTask(ctx) + } +} + +func (s *SKVMGuestInstance) onMonitorDisConnect(err error) { + log.Infof("On Monitor Disconnect") + s.CleanStartupTask() + s.scriptStop() + if !jsonutils.QueryBoolean(s.Desc, "is_slave", false) { + s.SyncStatus() + } + s.clearCgroup(0) + s.Monitor = nil +} + +func (s *SKVMGuestInstance) startQemuBuiltInNbdServer(ctx context.Context) { + nbdServerPort := s.manager.GetFreePortByBase(BUILT_IN_NBD_SERVER_PORT_BASE) + var onNbdServerStarted = func(res string) { + if len(res) > 0 { + log.Errorln("Start Qemu Builtin nbd server error %s", res) + hostutils.TaskFailed(ctx, res) + } else { + hostutils.TaskComplete(ctx, nil) + } + } + s.Monitor.StartNbdServer(nbdServerPort, true, true, onNbdServerStarted) +} + +func (s *SKVMGuestInstance) clearCgroup(pid int) { + if pid == 0 && s.cgroupPid > 0 { + pid = s.cgroupPid + } + log.Infof("cgroup destroy %d", pid) + if pid > 0 { + cgrouputils.CgroupDestroy(strconv.Itoa(pid)) + } +} + +func (s *SKVMGuestInstance) IsMaster() bool { + return jsonutils.QueryBoolean(s.Desc, "is_master", false) +} + +func (s *SKVMGuestInstance) DiskCount() int { + disks, _ := s.Desc.GetArray("disks") + return len(disks) +} + +func (s *SKVMGuestInstance) IsMirrorJobSucc() bool { + return s.mirrorJobSuccCount != nil && *s.mirrorJobSuccCount == s.DiskCount() +} + +func (s *SKVMGuestInstance) CleanStartupTask() { + log.Infof("Clean startup task ...") + if s.startupTask != nil { + s.startupTask.Stop() + s.startupTask = nil + } +} + +func (s *SKVMGuestInstance) onMonitorTimeout(ctx context.Context, err error) { + log.Errorf("Monitor connect timeout, VM %s frozen!! force restart!!!!", s.Id) + s.ForceStop() + timeutils2.AddTimeout(time.Second*3, + func() { s.asyncScriptStart(ctx, jsonutils.NewDict()) }) +} + +func (s *SKVMGuestInstance) GetHmpMonitorPort(vncPort int) int { + if vncPort <= 0 { + vncPort = s.GetVncPort() + } + if vncPort > 0 { + return vncPort + MONITOR_PORT_BASE + } else { + return -1 + } +} + +func (s *SKVMGuestInstance) GetQmpMonitorPort(vncPort int) int { + if vncPort <= 0 { + vncPort = s.GetVncPort() + } + if vncPort > 0 { + return vncPort + MONITOR_PORT_BASE + 200 + } else { + return -1 + } +} + +func (s *SKVMGuestInstance) GetVncPort() int { + if s.IsRunning() { + vncPort, err := ioutil.ReadFile(s.GetVncFilePath()) + if err != nil { + return -1 + } + strPort := strings.TrimSpace(string(vncPort)) + if len(strPort) > 0 { + port, err := strconv.Atoi(strPort) + if err == nil { + return port + } + } + } + return -1 +} + +func (s *SKVMGuestInstance) saveVncPort(port int) error { + return fileutils2.FilePutContents(s.GetVncFilePath(), fmt.Sprintf("%d", port), false) +} + +func (s *SKVMGuestInstance) DoResumeTask(ctx context.Context) { + s.startupTask = NewGuestResumeTask(ctx, s) + s.startupTask.Start() +} + +func (s *SKVMGuestInstance) SyncStatus() { + if s.IsRunning() { + s.Monitor.GetBlockJobs(s.CheckBlockOrRunning) + return + } + var status = "ready" + if s.IsSuspend() { + status = "suspend" + } + + hostutils.UpdateServerStatus(context.Background(), s.Id, status) +} + +func (s *SKVMGuestInstance) CheckBlockOrRunning(jobs int) { + var status = "running" + if jobs > 0 { + status = "block_stream" + } + _, err := hostutils.UpdateServerStatus(context.Background(), s.Id, status) + if err != nil { + log.Errorln(err) + } +} + +func (s *SKVMGuestInstance) SaveDesc(desc jsonutils.JSONObject) error { + var ok bool + s.Desc, ok = desc.(*jsonutils.JSONDict) + if !ok { + return fmt.Errorf("Unknown desc format, not JSONDict") + } + if err := fileutils2.FilePutContents(s.GetDescFilePath(), desc.String(), false); err != nil { + log.Errorln(err) + } + return nil +} + +func (s *SKVMGuestInstance) StartGuest(ctx context.Context, params jsonutils.JSONObject) { + hostutils.DelayTask(ctx, s.asyncScriptStart, params) +} + +func (s *SKVMGuestInstance) DeployFs(deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) { + disks, _ := s.Desc.GetArray("disks") + if len(disks) > 0 { + storageId, _ := disks[0].GetString("storage_id") + diskId, _ := disks[0].GetString("disk_id") + + disk := storageman.GetManager().GetStorageDisk(storageId, diskId) + return disk.DeployGuestFs(disk.GetPath(), s.Desc, deployInfo) + } else { + return nil, fmt.Errorf("Guest dosen't have disk ??") + } +} + +// Delay process +func (s *SKVMGuestInstance) CleanGuest(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + migrated, ok := params.(bool) + if !ok { + return nil, hostutils.ParamsError + } + if err := s.StartDelete(ctx, migrated); err != nil { + return nil, err + } + return nil, nil +} + +func (s *SKVMGuestInstance) StartDelete(ctx context.Context, migrated bool) error { + for s.IsRunning() { + s.ForceStop() + time.Sleep(time.Second * 1) + } + return s.Delete(ctx, migrated) +} + +func (s *SKVMGuestInstance) ForceStop() bool { + s.ExitCleanup(true) + if s.IsRunning() { + _, err := procutils.NewCommand("kill", "-9", fmt.Sprintf("%d", s.GetPid())).Run() + if err != nil { + log.Errorln(err) + return false + } + for _, f := range s.GetCleanFiles() { + _, err := procutils.NewCommand("rm", "-f", f).Run() + if err != nil { + log.Errorln(err) + return false + } + } + return true + } + return false +} + +func (s *SKVMGuestInstance) ExitCleanup(clear bool) { + if clear { + pid := s.GetPid() + if pid > 0 { + s.clearCgroup(pid) + } + } + if s.Monitor != nil { + s.Monitor.Disconnect() + s.Monitor = nil + } +} + +func (s *SKVMGuestInstance) CleanupCpuset() { + task := cgrouputils.NewCGroupCPUSetTask(strconv.Itoa(s.GetPid()), 0, "") + if !task.RemoveTask() { + log.Warningf("remove cpuset cgroup error: %s %s", s.Id, s.GetPid()) + } +} + +func (s *SKVMGuestInstance) GetCleanFiles() []string { + return []string{s.GetPidFilePath(), s.GetVncFilePath()} +} + +func (s *SKVMGuestInstance) delTmpDisks(ctx context.Context, migrated bool) error { + disks, _ := s.Desc.GetArray("disks") + for _, disk := range disks { + if disk.Contains("path") { + diskPath, _ := disk.GetString("path") + d := storageman.GetManager().GetDiskByPath(diskPath) + if d != nil && d.GetType() == storagetypes.STORAGE_LOCAL && migrated { + if err := d.DeleteAllSnapshot(); err != nil { + log.Errorln(err) + return err + } + if _, err := d.Delete(ctx, nil); err != nil { + log.Errorln(err) + return err + } + } + } + } + return nil +} + +func (s *SKVMGuestInstance) Delete(ctx context.Context, migrated bool) error { + if err := s.delTmpDisks(ctx, migrated); err != nil { + return err + } + _, err := procutils.NewCommand("rm", "-rf", s.HomeDir()).Run() + return err +} + +func (s *SKVMGuestInstance) Stop() bool { + s.ExitCleanup(true) + if s.scriptStop() { + return true + } else { + return false + } +} + +func (s *SKVMGuestInstance) scriptStart() error { + _, err := procutils.NewCommand("sh", s.GetStartScriptPath()).Run() + if err != nil { + s.scriptStop() + return err + } + return nil +} + +func (s *SKVMGuestInstance) scriptStop() bool { + _, err := procutils.NewCommand("sh", s.GetStopScriptPath()).Run() + if err != nil { + log.Errorln(err) + return false + } + return true +} + +func (s *SKVMGuestInstance) ExecStopTask(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + timeout, ok := params.(int64) + if !ok { + return nil, hostutils.ParamsError + } + NewGuestStopTask(s, ctx, timeout).Start() + return nil, nil +} + +func (s *SKVMGuestInstance) ExecSuspendTask(ctx context.Context) { + // TODO +} + +func (s *SKVMGuestInstance) GetNicDescMatch(mac, ip, port, bridge string) jsonutils.JSONObject { + nics, _ := s.Desc.GetArray("nics") + for _, nic := range nics { + nicMac, _ := nic.GetString("mac") + nicIp, _ := nic.GetString("ip") + nicPort, _ := nic.GetString("ifname") + nicBridge, _ := nic.GetString("bridge") + if (len(mac) == 0 || netutils2.MacEqual(nicMac, mac)) && + (len(ip) == 0 || nicIp == ip) && + (len(port) == 0 || nicPort == port) && + (len(bridge) == 0 || nicBridge == bridge) { + return nic + } + } + return nil +} + +func pathEqual(disk, ndisk jsonutils.JSONObject) bool { + if disk.Contains("path") && ndisk.Contains("path") { + path1, _ := disk.GetString("path") + path2, _ := ndisk.GetString("path") + return path1 == path2 + } else if disk.Contains("url") && ndisk.Contains("url") { + path1, _ := disk.GetString("assumed_path") + path2, _ := ndisk.GetString("assumed_path") + return path1 == path2 + } else { + return false + } + +} + +func (s *SKVMGuestInstance) compareDescDisks(newDesc jsonutils.JSONObject) ([]jsonutils.JSONObject, []jsonutils.JSONObject) { + var delDisks, addDisks = []jsonutils.JSONObject{}, []jsonutils.JSONObject{} + newDisks, _ := newDesc.GetArray("disks") + for _, disk := range newDisks { + driver, _ := disk.GetString("driver") + if utils.IsInStringArray(driver, []string{"virtio", "scsi"}) { + addDisks = append(addDisks, disk) + } + } + oldDisks, _ := s.Desc.GetArray("disks") + for _, disk := range oldDisks { + driver, _ := disk.GetString("driver") + if utils.IsInStringArray(driver, []string{"virtio", "scsi"}) { + var find = false + for idx, ndisk := range addDisks { + diskIndex, _ := disk.Int("index") + nDiskIndex, _ := ndisk.Int("index") + if diskIndex == nDiskIndex && pathEqual(disk, ndisk) { + addDisks = append(addDisks[:idx], addDisks[idx+1:]...) + find = true + break + } + } + if !find { + delDisks = append(delDisks, disk) + } + } + } + return delDisks, addDisks +} + +func (s *SKVMGuestInstance) compareDescCdrom(newDesc jsonutils.JSONObject) *string { + if !s.Desc.Contains("cdrom") && !newDesc.Contains("cdrom") { + return nil + } else if !s.Desc.Contains("cdrom") && newDesc.Contains("cdrom") { + cdrom, _ := newDesc.GetString("cdrom", "path") + return &cdrom + } else if s.Desc.Contains("cdrom") && !newDesc.Contains("cdrom") { + var res = "" + return &res + } else { + cdrom, _ := s.Desc.GetString("cdrom", "path") + ncdrom, _ := newDesc.GetString("cdrom", "path") + if cdrom == ncdrom { + return nil + } else { + return &ncdrom + } + } +} + +func (s *SKVMGuestInstance) compareDescNetworks(newDesc jsonutils.JSONObject) ([]jsonutils.JSONObject, []jsonutils.JSONObject) { + var isValid = func(net jsonutils.JSONObject) bool { + driver, _ := net.GetString("driver") + return driver == "virtio" + } + + var findNet = func(nets []jsonutils.JSONObject, net jsonutils.JSONObject) int { + mac1, _ := net.GetString("mac") + for i := 0; i < len(nets); i++ { + mac2, _ := nets[i].GetString("mac") + if mac1 == mac2 { + return i + } + } + return -1 + } + + var delNics, addNics = []jsonutils.JSONObject{}, []jsonutils.JSONObject{} + nics, _ := newDesc.GetArray("nics") + for _, n := range nics { + if isValid(n) { + addNics = append(addNics, n) + } + } + + nics, _ = newDesc.GetArray("nics") + for _, n := range nics { + if isValid(n) { + idx := findNet(addNics, n) + if idx >= 0 { + // remove n + addNics = append(addNics[:idx], addNics[idx+1:]...) + } else { + delNics = append(delNics, n) + } + } + } + return delNics, addNics +} + +// 目测sync_cgroup没有要用到,先不写 +func (s *SKVMGuestInstance) SyncConfig(ctx context.Context, desc jsonutils.JSONObject, fwOnly bool) (jsonutils.JSONObject, error) { + var delDisks, addDisks, delNetworks, addNetworks []jsonutils.JSONObject + var cdrom *string + + if !fwOnly { + delDisks, addDisks = s.compareDescDisks(desc) + cdrom = s.compareDescCdrom(desc) + delNetworks, addNetworks = s.compareDescNetworks(desc) + } + if err := s.SaveDesc(desc); err != nil { + return nil, err + } + + if !s.IsRunning() { + return nil, nil + } + + vncPort := s.GetVncPort() + data := jsonutils.NewDict() + data.Set("vnc_port", jsonutils.NewInt(int64(vncPort))) + s.saveScripts(data) + + // if options.enable_openflow_controller: 不写 + + if fwOnly { + res := jsonutils.NewDict() + res.Set("task", jsonutils.NewArray(jsonutils.NewString("secgroupsync"))) + return res, nil + } + var runTaskNames = []jsonutils.JSONObject{} + var tasks = []IGuestTasks{} + + var callBack = func(errs []error) { + if len(errs) == 0 { + hostutils.TaskComplete(ctx, nil) + } else { + var reason string + for _, err := range errs { + reason += "; " + err.Error() + } + hostutils.TaskFailed(ctx, reason[2:]) + } + } + + if len(delDisks)+len(addDisks) > 0 || cdrom != nil { + task := NewGuestDiskSyncTask(s, delDisks, addDisks, cdrom) + runTaskNames = append(runTaskNames, jsonutils.NewString("disksync")) + tasks = append(tasks, task) + } + + if len(delNetworks)+len(addNetworks) > 0 { + task := NewGuestNetworkSyncTask(s, delNetworks, addNetworks) + runTaskNames = append(runTaskNames, jsonutils.NewString("networksync")) + tasks = append(tasks, task) + } + + NewGuestSyncConfigTaskExecutor(ctx, s, tasks, callBack).Start(1) + res := jsonutils.NewDict() + res.Set("task", jsonutils.NewArray(runTaskNames...)) + return res, nil +} + +func (s *SKVMGuestInstance) getApptags() []string { + var tags []string + meta, _ := s.Desc.Get("metadata") + if meta != nil && meta.Contains("app_tags") { + tagsStr, _ := meta.GetString("app_tags") + if len(tagsStr) > 0 { + return strings.Split(tagsStr, ",") + } + } + return tags +} + +func (s *SKVMGuestInstance) getStorageDeviceId() string { + disks, _ := s.Desc.GetArray("disks") + if len(disks) > 0 { + diskPath, _ := disks[0].GetString("path") + if len(diskPath) > 0 { + return fileutils2.GetDevId(diskPath) + } + } + return "" +} + +func (s *SKVMGuestInstance) SetCgroup() { + s.cgroupPid = s.GetPid() + s.setCgroupIo() + s.setCgroupCpu() +} + +func (s *SKVMGuestInstance) setCgroupIo() { + appTags := s.getApptags() + params := map[string]int{} + if utils.IsInStringArray("io_hardlimit", appTags) { + devId := s.getStorageDeviceId() + if len(devId) == 0 { + log.Errorln("failed to get device ID (MAJOR:MINOR)") + return + } + params["blkio.throttle.read_bps_device"] = options.HostOptions.DefaultReadBpsPerCpu + params["blkio.throttle.read_iops_device"] = options.HostOptions.DefaultReadIopsPerCpu + params["blkio.throttle.write_bps_device"] = options.HostOptions.DefaultWriteBpsPerCpu + params["blkio.throttle.write_iops_device"] = options.HostOptions.DefaultWriteIopsPerCpu + cpu, _ := s.Desc.Int("cpu") + cgrouputils.CgroupIoHardlimitSet(strconv.Itoa(s.cgroupPid), int(cpu), params, devId) + } +} + +func (s *SKVMGuestInstance) setCgroupCpu() { + cpu, _ := s.Desc.Int("cpu") + cgrouputils.CgroupSet(strconv.Itoa(s.cgroupPid), int(cpu)) + + // TODO XXX + /* + var ( + cpuWeight = 1024 + cpuPeriod = 0 + cpuQuota = 0 + appTags = s.getApptags() + meta, _ = s.Desc.Get("metadata") + ) + + if meta != nil { + if meta.Contains("__cpu_weight") { + cpuWeight, _ = meta.Int("__cpu_weight") + } + if meta.Contains("__cpu_period") { + cpuPeriod, _ = meta.Int("__cpu_period") + } else { + cpuPeriod = -1 + } + if meta.Contains("__cpu_quota") { + cpuQuota, _ = meta.Int("__cpu_quota") + } else { + cpuQuota = -1 + } + } + */ +} + +func (s *SKVMGuestInstance) CreateFromDesc(desc jsonutils.JSONObject) error { + if err := s.PrepareDir(); err != nil { + uuid, _ := desc.GetString("uuid") + return fmt.Errorf("Failed to create server dir %s", uuid) + } + return s.SaveDesc(desc) +} + +func (s *SKVMGuestInstance) GetNeedMergeBackingFileDiskIndexs() []int { + res := make([]int, 0) + disks, _ := s.Desc.GetArray("disks") + for _, disk := range disks { + if jsonutils.QueryBoolean(disk, "merge_snapshot", false) { + diskIdx, _ := disk.Int("index") + res = append(res, int(diskIdx)) + } + } + return res +} + +func (s *SKVMGuestInstance) streamDisksComplete(ctx context.Context) { + disks, _ := s.Desc.GetArray("disks") + for i, disk := range disks { + diskpath, _ := disk.GetString("path") + d := storageman.GetManager().GetDiskByPath(diskpath) + if d != nil { + d.PostCreateFromImageFuse() + } + if jsonutils.QueryBoolean(disk, "merge_snapshot", false) { + d := disks[i].(*jsonutils.JSONDict) + d.Set("merge_snapshot", jsonutils.JSONFalse) + } + } + s.SaveDesc(s.Desc) + _, err := modules.Servers.PerformAction(hostutils.GetComputeSession(ctx), + s.Id, "stream-disks-complete", nil) + if err != nil { + log.Infof("stream disks complete sync error %s", err) + } +} + +func (s *SKVMGuestInstance) GetQemuVersionStr() string { + return s.QemuVersion +} + +func (s *SKVMGuestInstance) SyncMetadata(meta *jsonutils.JSONDict) { + _, err := modules.Servers.SetMetadata(hostutils.GetComputeSession(context.Background()), + s.Id, meta) + if err != nil { + log.Errorln(err) + } +} + +func (s *SKVMGuestInstance) SetVncPassword() { + password := seclib.RandomPassword(8) + s.VncPassword = password + var callback = func(res string) { + if len(res) > 0 { + log.Errorf("Set vnc password failed: %s", res) + } + } + timeutils2.AddTimeout(time.Second*3, + func() { s.Monitor.SetVncPassword(s.GetVdiProtocol(), password, callback) }) +} + +func (s *SKVMGuestInstance) ListStateFilePaths() []string { + var ret = []string{} + if fileutils2.Exists(s.HomeDir()) { + files, err := ioutil.ReadDir(s.HomeDir()) + if err != nil { + log.Errorln(err) + return nil + } + for _, f := range files { + if strings.HasPrefix(f.Name(), STATE_FILE_PREFIX) { + ret = append(ret, path.Join(s.HomeDir(), f.Name())) + } + } + } + return ret +} + +// 好像不用了 +func (s *SKVMGuestInstance) CleanStatefiles() { + for _, stateFile := range s.ListStateFilePaths() { + if _, err := procutils.NewCommand("mountpoint", stateFile).Run(); err == nil { + if _, err = procutils.NewCommand("umount", stateFile).Run(); err != nil { + log.Errorln(err) + } + } + if _, err := procutils.NewCommand("rm", "-rf", stateFile).Run(); err != nil { + log.Errorln(err) + } + } + if _, err := procutils.NewCommand("rm", "-rf", s.GetFuseTmpPath()).Run(); err != nil { + log.Errorln(err) + } +} + +func (s *SKVMGuestInstance) GetFuseTmpPath() string { + return path.Join(s.HomeDir(), "tmp") +} + +func (s *SKVMGuestInstance) StreamDisks(ctx context.Context, callback func(), disksIdx []int) { + log.Infof("Start guest block stream task ...") + task := NewGuestStreamDisksTask(ctx, s, callback, disksIdx) + task.Start() +} + +func (s *SKVMGuestInstance) isLiveSnapshotEnabled() bool { + if version.GE(s.QemuVersion, "2.12.1") { + return true + } else { + return false + } +} + +func (s *SKVMGuestInstance) ExecReloadDiskTask(ctx context.Context, disk storageman.IDisk) (jsonutils.JSONObject, error) { + if s.IsRunning() { + if s.isLiveSnapshotEnabled() { + task := NewGuestReloadDiskTask(ctx, s, disk) + return nil, task.WaitSnapshotReplaced(task.Start) + } else { + return nil, fmt.Errorf("Guest dosen't support reload disk") + } + } else { + res := jsonutils.NewDict() + res.Set("reopen", jsonutils.JSONTrue) + return res, nil + } +} + +func (s *SKVMGuestInstance) ExecDiskSnapshotTask( + ctx context.Context, disk storageman.IDisk, snapshotId string, +) (jsonutils.JSONObject, error) { + if s.IsRunning() { + if !s.isLiveSnapshotEnabled() { + return nil, fmt.Errorf("Guest dosen't support live snapshot") + } + err := disk.CreateSnapshot(snapshotId) + if err != nil { + return nil, err + } + task := NewGuestDiskSnapshotTask(ctx, s, disk, snapshotId) + task.Start() + return nil, nil + } else { + return s.StaticSaveSnapshot(ctx, disk, snapshotId) + } +} + +func (s *SKVMGuestInstance) StaticSaveSnapshot( + ctx context.Context, disk storageman.IDisk, snapshotId string, +) (jsonutils.JSONObject, error) { + err := disk.CreateSnapshot(snapshotId) + if err != nil { + return nil, err + } + location := path.Join(disk.GetSnapshotDir(), snapshotId) + res := jsonutils.NewDict() + res.Set("localtion", jsonutils.NewString(location)) + return res, nil +} + +func (s *SKVMGuestInstance) ExecDeleteSnapshotTask( + ctx context.Context, disk storageman.IDisk, + deleteSnapshot string, convertSnapshot string, pendingDelete bool, +) (jsonutils.JSONObject, error) { + if s.IsRunning() { + if s.isLiveSnapshotEnabled() { + task := NewGuestSnapshotDeleteTask(ctx, s, disk, + deleteSnapshot, convertSnapshot, pendingDelete) + task.Start() + return nil, nil + } else { + return nil, fmt.Errorf("Guest dosen't support live snapshot delete") + } + } else { + return s.deleteStaticSnapshotFile(ctx, disk, deleteSnapshot, + convertSnapshot, pendingDelete) + } +} + +func (s *SKVMGuestInstance) deleteStaticSnapshotFile( + ctx context.Context, disk storageman.IDisk, + deleteSnapshot string, convertSnapshot string, pendingDelete bool, +) (jsonutils.JSONObject, error) { + if err := disk.DeleteSnapshot(deleteSnapshot, convertSnapshot, pendingDelete); err != nil { + log.Errorln(err) + return nil, err + } + res := jsonutils.NewDict() + res.Set("deleted", jsonutils.JSONTrue) + return res, nil +} + +func (s *SKVMGuestInstance) PrepareMigrate(liveMigrage bool) (*jsonutils.JSONDict, error) { + disksBackFile := jsonutils.NewDict() + disks, _ := s.Desc.GetArray("disks") + for _, disk := range disks { + if disk.Contains("path") { + diskPath, _ := disk.GetString("path") + d := storageman.GetManager().GetDiskByPath(diskPath) + if d.GetType() == storagetypes.STORAGE_LOCAL { + back, err := d.PrepareMigrate(liveMigrage) + if err != nil { + return nil, err + } + if len(back) > 0 { + diskId, _ := disk.GetString("disk_id") + disksBackFile.Set(diskId, jsonutils.NewString(back)) + } + } + } + } + return disksBackFile, nil +} + +func (s *SKVMGuestInstance) onlineResizeDisk(ctx context.Context, diskId string, sizeMB int64) { + task := NewGuestOnlineResizeDiskTask(ctx, s, diskId, sizeMB) + task.Start() +} diff --git a/pkg/hostman/guestman/qemu-kvm_test.go b/pkg/hostman/guestman/qemu-kvm_test.go new file mode 100644 index 0000000000..40eb6c2704 --- /dev/null +++ b/pkg/hostman/guestman/qemu-kvm_test.go @@ -0,0 +1,11 @@ +package guestman + +import ( + "testing" +) + +func TestSKVMGuestInstance_getPid(t *testing.T) { + manager := NewGuestManager(nil, "/opt/cloud/workspace/servers") + s := NewKVMGuestInstance("05b787e9-b78e-4ebc-8128-04f55d37306f", manager) + t.Logf("Guest is ->> %d", s.GetPid()) +} diff --git a/pkg/hostman/guestman/qemu-kvmhelper.go b/pkg/hostman/guestman/qemu-kvmhelper.go new file mode 100644 index 0000000000..b0159b89de --- /dev/null +++ b/pkg/hostman/guestman/qemu-kvmhelper.go @@ -0,0 +1,680 @@ +package guestman + +import ( + "fmt" + "net" + "path" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudcommon/ethernet" + "yunion.io/x/onecloud/pkg/cloudcommon/ethernet/arp" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/qemutils" +) + +const ( + OS_NAME_LINUX = "Linux" + OS_NAME_WINDOWS = "Windows" + OS_NAME_MACOS = "macOS" + OS_NAME_ANDROID = "Android" + OS_NAME_VMWARE = "VMWare" + + MODE_READLINE = "readline" + MODE_CONTROL = "control" + + DISK_DRIVER_VIRTIO = "virtio" + DISK_DRIVER_SCSI = "scsi" + DISK_DRIVER_PVSCSI = "pvscsi" + DISK_DRIVER_IDE = "ide" + DISK_DRIVER_SATA = "sata" +) + +func (s *SKVMGuestInstance) IsKvmSupport() bool { + return guestManger.GetHost().IsKvmSupport() +} + +func (s *SKVMGuestInstance) IsVdiSpice() bool { + vdi, _ := s.Desc.GetString("vdi") + return vdi == "spice" +} + +func (s *SKVMGuestInstance) getMonitorDesc(idstr string, port int, mode string) string { + var cmd = "" + cmd += fmt.Sprintf(" -chardev socket,id=%sdev", idstr) + cmd += fmt.Sprintf(",port=%d", port) + cmd += ",host=127.0.0.1,nodelay,server,nowait" + cmd += fmt.Sprintf(" -mon chardev=%sdev,id=%s,mode=%s", idstr, idstr, mode) + return cmd +} + +func (s *SKVMGuestInstance) getOsname() string { + if s.Desc.Contains("metadata") { + metadata, _ := s.Desc.Get("metadata") + if metadata.Contains("os_name") { + osname, _ := metadata.GetString("os_name") + return osname + } + } + return OS_NAME_LINUX +} + +func (s *SKVMGuestInstance) getMachine() string { + machine, err := s.Desc.GetString("machine") + if err != nil { + machine = "pc" + } + return machine +} + +func (s *SKVMGuestInstance) getBios() string { + bios, err := s.Desc.GetString("bios") + if err != nil { + bios = "bios" + } + return bios +} + +func (s *SKVMGuestInstance) isQ35() bool { + return s.getMachine() == "q35" +} + +func (s *SKVMGuestInstance) GetVdiProtocol() string { + vdi, err := s.Desc.GetString("vdi") + if err != nil { + vdi = "vnc" + } + return vdi +} + +func (s *SKVMGuestInstance) GetPciBus() string { + if s.isQ35() { + return "pcie.0" + } else { + return "pci.0" + } +} + +func (s *SKVMGuestInstance) getDriveDesc(disk jsonutils.JSONObject, format string) string { + diskIndex, _ := disk.Int("index") + cacheMode, _ := disk.GetString("cache_mode") + aioMode, _ := disk.GetString("aio_mode") + + cmd := " -drive" + cmd += fmt.Sprintf(" file=$DISK_%d", diskIndex) + cmd += ",if=none" + cmd += fmt.Sprintf(",id=drive_%d", diskIndex) + if len(format) == 0 || format == "qcow2" { + // pass # qemu will automatically detect image format + } else if format == "raw" { + cmd += ",format=raw" + } + cmd += fmt.Sprintf(",cache=%s", cacheMode) + cmd += fmt.Sprintf(",aio=%s", aioMode) + if disk.Contains("url") { // # a remote file backed image + cmd += ",copy-on-read=on" + } + // #cmd += ",media=disk" + return cmd +} + +func (s *SKVMGuestInstance) GetDiskAddr(idx int) int { + var base = 5 + if s.IsVdiSpice() { + base += 10 + } + return base + idx +} + +func (s *SKVMGuestInstance) GetDiskDeviceModel(driver string) string { + if driver == DISK_DRIVER_VIRTIO { + return "virtio-blk-pci" + } else if utils.IsInStringArray(driver, []string{DISK_DRIVER_SCSI, DISK_DRIVER_PVSCSI}) { + return "scsi-hd" + } else if driver == DISK_DRIVER_IDE { + return "ide-hd" + } else if driver == DISK_DRIVER_SATA { + return "ide-drive" + } else { + return "None" + } +} + +func (s *SKVMGuestInstance) getVdiskDesc(disk jsonutils.JSONObject) string { + diskIndex, _ := disk.Int("index") + diskDriver, _ := disk.GetString("driver") + + var cmd = "" + cmd += fmt.Sprintf(" -device %s", s.GetDiskDeviceModel(diskDriver)) + cmd += fmt.Sprintf(",drive=drive_%d", diskIndex) + if diskDriver == DISK_DRIVER_VIRTIO { + cmd += fmt.Sprintf(",bus=%s,addr=0x%x", s.GetPciBus(), s.GetDiskAddr(int(diskIndex))) + } else if utils.IsInStringArray(diskDriver, []string{DISK_DRIVER_SCSI, DISK_DRIVER_PVSCSI}) { + cmd += ",bus=scsi.0" + } else if diskDriver == DISK_DRIVER_IDE { + cmd += fmt.Sprintf(",bus=ide.%d,unit=%d", diskIndex/2, diskIndex%2) + } else if diskDriver == DISK_DRIVER_SATA { + cmd += fmt.Sprintf(",bus=ide.%d", diskIndex) + } + cmd += fmt.Sprintf(",id=drive_%d", diskIndex) + return cmd +} + +func (s *SKVMGuestInstance) getNicUpScriptPath(nic jsonutils.JSONObject) string { + ifname, _ := nic.GetString("ifname") + bridge, _ := nic.GetString("bridge") + return path.Join(s.HomeDir(), fmt.Sprintf("if-up-%s-%s.sh", bridge, ifname)) +} + +func (s *SKVMGuestInstance) getNicDownScriptPath(nic jsonutils.JSONObject) string { + ifname, _ := nic.GetString("ifname") + bridge, _ := nic.GetString("bridge") + return path.Join(s.HomeDir(), fmt.Sprintf("if-down-%s-%s.sh", bridge, ifname)) +} + +func (s *SKVMGuestInstance) generateNicScripts(nic jsonutils.JSONObject) error { + bridge, _ := nic.GetString("bridge") + dev := guestManger.GetHost().GetBridgeDev(bridge) + if dev == nil { + return fmt.Errorf("Can't find bridge %s", bridge) + } + if err := dev.GenerateIfupScripts(s.getNicUpScriptPath(nic), nic); err != nil { + log.Errorln(err) + return err + } + if err := dev.GenerateIfdownScripts(s.getNicDownScriptPath(nic), nic); err != nil { + log.Errorln(err) + return err + } + return nil +} + +func (s *SKVMGuestInstance) getNetdevDesc(nic jsonutils.JSONObject) (string, error) { + ifname, _ := nic.GetString("ifname") + driver, _ := nic.GetString("driver") + + if err := s.generateNicScripts(nic); err != nil { + return "", err + } + upscript := s.getNicUpScriptPath(nic) + downscript := s.getNicDownScriptPath(nic) + cmd := " -netdev type=tap" + cmd += fmt.Sprintf(",id=%s", ifname) + cmd += fmt.Sprintf(",ifname=%s", ifname) + if driver == "virtio" && s.IsKvmSupport() { + cmd += ",vhost=on,vhostforce=off" + } + cmd += fmt.Sprintf(",script=%s", upscript) + cmd += fmt.Sprintf(",downscript=%s", downscript) + return cmd, nil +} + +func (s *SKVMGuestInstance) getNicDeviceModel(name string) string { + if name == "virtio" { + return "virtio-net-pci" + } else if name == "e1000" { + return "e1000-82545em" + } else { + return name + } +} + +func (s *SKVMGuestInstance) getNicAddr(index int) int { + var diskCnt = 10 + disks, _ := s.Desc.GetArray("disks") + if len(disks) > 10 { + diskCnt = 20 + } + return s.GetDiskAddr(diskCnt + index) +} + +func (s *SKVMGuestInstance) getVnicDesc(nic jsonutils.JSONObject) string { + ifname, _ := nic.GetString("ifname") + driver, _ := nic.GetString("driver") + mac, _ := nic.GetString("mac") + index, _ := nic.Int("index") + vectors, _ := nic.Int("vectors") + bw, _ := nic.Int("bw") + + cmd := fmt.Sprintf(" -device %s", s.getNicDeviceModel(driver)) + cmd += fmt.Sprintf(",netdev=%s", ifname) + cmd += fmt.Sprintf(",mac=%s", mac) + + cmd += fmt.Sprintf(",addr=0x%x", s.getNicAddr(int(index))) + if driver == "virtio" { + if nic.Contains("vectors") { + cmd += fmt.Sprintf(",vectors=%d", vectors) + } + cmd += fmt.Sprintf("$(nic_speed %d)", bw) + } + return cmd +} + +func (s *SKVMGuestInstance) getQgaDesc() string { + cmd := " -chardev socket,path=" + cmd += path.Join(s.HomeDir(), "qga.sock") + cmd += ",server,nowait,id=qga0" + cmd += " -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0" + return cmd +} + +func (s *SKVMGuestInstance) generateStartScript(data *jsonutils.JSONDict) (string, error) { + var ( + uuid, _ = s.Desc.GetString("uuid") + mem, _ = s.Desc.Int("mem") + cpu, _ = s.Desc.Int("cpu") + name, _ = s.Desc.GetString("name") + nics, _ = s.Desc.GetArray("nics") + disks, _ = s.Desc.GetArray("disks") + osname = s.getOsname() + cmd = "" + ) + + if osname == OS_NAME_MACOS { + s.Desc.Set("machine", jsonutils.NewString("q35")) + s.Desc.Set("bios", jsonutils.NewString("UEFI")) + } + + vncPort, _ := data.Int("vnc_port") + + qemuVersion := options.HostOptions.DefaultQemuVersion + if data.Contains("qemu_version") { + qemuVersion, _ = data.GetString("qemu_version") + } + if qemuVersion == "latest" { + qemuVersion = "" + } + + var devAddrs = []string{} + isolatedParams, err := s.Desc.GetArray("isolated_devices") + if err != nil { + for _, params := range isolatedParams { + devAddr, _ := params.GetString("addr") + devAddrs = append(devAddrs, devAddr) + } + } + isolatedDevsParams := s.manager.GetHost().GetIsolatedDeviceManager().GetQemuParams(devAddrs) + + for _, nic := range nics { + downscript := s.getNicDownScriptPath(nic) + ifname, _ := nic.GetString("ifnam") + cmd += fmt.Sprintf("%s %s\n", downscript, ifname) + } + + if options.HostOptions.HugepagesOption == "native" { + cmd += fmt.Sprintf("mkdir -p /dev/hugepages/%s\n", uuid) + cmd += fmt.Sprintf("mount -t hugetlbfs -o size=%dM hugetlbfs-%s /dev/hugepages/%s\n", + mem, uuid, uuid) + } + + cmd += "sleep 1\n" + cmd += fmt.Sprintf("echo %d > %s\n", vncPort, s.GetVncFilePath()) + + for _, disk := range disks { + diskPath, _ := disk.GetString("path") + d := storageman.GetManager().GetDiskByPath(diskPath) + if d == nil { + return "", fmt.Errorf("get disk %s by storage error", diskPath) + } + + diskIndex, _ := disk.Int("index") + cmd += d.GetDiskSetupScripts(int(diskIndex)) + } + + cmd += fmt.Sprintf("STATE_FILE=`ls -d %s* | head -n 1`\n", s.getStateFilePathRootPrefix()) + + var qemuCmd = qemutils.GetQemu(qemuVersion) + cmd += fmt.Sprintf("DEFAULT_QEMU_CMD='%s'\n", qemuCmd) + cmd += "if [ -n \"$STATE_FILE\" ]; then\n" + cmd += " QEMU_VER=`echo $STATE_FILE" + + ` | grep -o '_[[:digit:]]\+\.[[:digit:]]\+.*'` + "`\n" + cmd += " QEMU_CMD=\"qemu-system-x86_64\"\n" + cmd += " QEMU_LOCAL_PATH=\"/usr/local/bin/$QEMU_CMD\"\n" + cmd += " QEMU_LOCAL_PATH_VER=\"/usr/local/qemu-$QEMU_VER/bin/$QEMU_CMD\"\n" + cmd += " QEMU_BIN_PATH=\"/usr/bin/$QEMU_CMD\"\n" + cmd += " if [ -f \"$QEMU_LOCAL_PATH_VER\" ]; then\n" + cmd += " QEMU_CMD=$QEMU_LOCAL_PATH_VER\n" + cmd += " elif [ -f \"$QEMU_LOCAL_PATH\" ]; then\n" + cmd += " QEMU_CMD=$QEMU_LOCAL_PATH\n" + cmd += " elif [ -f \"$QEMU_BIN_PATH\" ]; then\n" + cmd += " QEMU_CMD=$QEMU_BIN_PATH\n" + cmd += " fi\n" + cmd += "else\n" + cmd += " QEMU_CMD=$DEFAULT_QEMU_CMD\n" + cmd += "fi\n" + cmd += "function nic_speed() {\n" + cmd += " $QEMU_CMD " + + if s.IsKvmSupport() { + cmd += "-enable-kvm" + } else { + cmd += "-no-kvm" + } + + cmd += " -device virtio-net-pci,? 2>&1 | grep .speed= > /dev/null\n" + cmd += " if [ \"$?\" -eq \"0\" ]; then\n" + cmd += " echo \",speed=$1\"\n" + cmd += " fi\n" + cmd += "}\n" + + // Generate Start VM script + cmd += `CMD="$QEMU_CMD` + var accel, cpuType string + if s.IsKvmSupport() { + cmd += " -enable-kvm" + accel = "kvm" + cpuType = "" + if osname == OS_NAME_MACOS { + cpuType = "Penryn,vendor=GenuineIntel" + } else { + cpuType = "host" + } + + if !guestManger.GetHost().IsNestedVirtualization() { + cpuType += ",kvm=off" + } + + if isolatedDevsParams != nil && len(isolatedDevsParams.Cpu) > 0 { + cpuType = isolatedDevsParams.Cpu + } + } else { + cmd += " -no-kvm" + accel = "tcg" + cpuType = "qemu64" + } + + cmd += fmt.Sprintf(" -cpu %s", cpuType) + + // TODO hmp - - + cmd += s.getMonitorDesc("hmqmon", s.GetHmpMonitorPort(int(vncPort)), MODE_READLINE) + if options.HostOptions.EnableQmpMonitor { + cmd += s.getMonitorDesc("qmqmon", s.GetQmpMonitorPort(int(vncPort)), MODE_CONTROL) + } + + cmd += " -rtc base=utc,clock=host,driftfix=none" + cmd += " -daemonize" + cmd += " -nodefaults -nodefconfig" + cmd += " -no-kvm-pit-reinjection" + cmd += " -global kvm-pit.lost_tick_policy=discard" + cmd += fmt.Sprintf(" -machine %s,accel=%s", s.getMachine(), accel) + cmd += " -k en-us" + // #cmd += " -g 800x600" + cmd += fmt.Sprintf(" -smp %d", cpu) + cmd += fmt.Sprintf(" -name %s", name) + // #cmd += fmt.Sprintf(" -uuid %s", self.desc["uuid"]) + cmd += fmt.Sprintf(" -m %d", mem) + + if options.HostOptions.HugepagesOption == "native" { + cmd += fmt.Sprintf(" -mem-prealloc -mem-path %s", fmt.Sprintf("/dev/hugepages/%s", uuid)) + } + + bootOrder, _ := s.Desc.GetString("boot_order") + cmd += fmt.Sprintf(" -boot order=%s", bootOrder) + + if s.getBios() == "UEFI" { + cmd += fmt.Sprintf(" -bios %s", options.HostOptions.OvmfPath) + } + + if osname == OS_NAME_MACOS { + cmd += " -device isa-applesmc,osk=ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc" + for i := 0; i < len(disks); i++ { + disk := disks[i].(*jsonutils.JSONDict) + disk.Set("driver", jsonutils.NewString(DISK_DRIVER_SATA)) + } + for i := 0; i < len(nics); i++ { + nic := nics[i].(*jsonutils.JSONDict) + nic.Set("vectors", jsonutils.NewInt(0)) + nic.Set("driver", jsonutils.NewString("e1000")) + } + } else if osname == OS_NAME_ANDROID { + if len(nics) > 1 { + s.Desc.Set("nics", jsonutils.NewArray(nics[0])) + } + nics, _ = s.Desc.GetArray("nics") + } + + cmd += " -device virtio-serial" + cmd += " -usb" + cmd += " -device usb-kbd" + // # if osname == self.OS_NAME_ANDROID: + // # cmd += " -device usb-mouse" + // # else: + cmd += " -device usb-tablet" + + if s.IsVdiSpice() { + cmd += " -device qxl-vga,id=video0,ram_size=141557760,vram_size=141557760" + cmd += " -device intel-hda,id=sound0" + cmd += " -device hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0" + cmd += fmt.Sprintf(" -spice port=%d,password=87654312,seamless-migration=on", 5900+vncPort) + // # ,streaming-video=all,playback-compression=on,jpeg-wan-compression=always,zlib-glz-wan-compression=always,image-compression=glz" % (5900+vnc_port) + cmd += fmt.Sprintf(" -device virtio-serial-pci,id=virtio-serial0,max_ports=16,bus=%s", s.GetPciBus()) + cmd += " -chardev spicevmc,name=vdagent,id=vdagent" + cmd += " -device virtserialport,nr=1,bus=virtio-serial0.0,chardev=vdagent,name=com.redhat.spice.0" + + // # usb redirect + cmd += " -device ich9-usb-ehci1,id=usb" + cmd += " -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,multifunction=on" + cmd += " -device ich9-usb-uhci2,masterbus=usb.0,firstport=2" + cmd += " -device ich9-usb-uhci3,masterbus=usb.0,firstport=4" + cmd += " -chardev spicevmc,name=usbredir,id=usbredirchardev1" + cmd += " -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1" + cmd += " -chardev spicevmc,name=usbredir,id=usbredirchardev2" + cmd += " -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2" + } else { + if isolatedDevsParams != nil && len(isolatedDevsParams.Vga) > 0 { + cmd += isolatedDevsParams.Vga + } else { + vga, err := s.Desc.GetString("vga") + if err != nil { + vga = "std" + } + cmd += fmt.Sprintf(" -vga %s", vga) + } + cmd += fmt.Sprintf(" -vnc :%d", vncPort) + if options.HostOptions.SetVncPassword { + cmd += ",password" + } + } + + var diskDrivers = []string{} + for _, disk := range disks { + driver, _ := disk.GetString("driver") + diskDrivers = append(diskDrivers, driver) + } + + if utils.IsInStringArray(DISK_DRIVER_SCSI, diskDrivers) { + cmd += " -device virtio-scsi-pci,id=scsi" + } else if utils.IsInStringArray(DISK_DRIVER_PVSCSI, diskDrivers) { + cmd += " -device pvscsi,id=scsi" + } + + for _, disk := range disks { + format, _ := disk.GetString("format") + cmd += s.getDriveDesc(disk, format) + cmd += s.getVdiskDesc(disk) + } + + if isolatedDevsParams != nil { + for _, each := range isolatedDevsParams.Devices { + cmd += each + } + } + + if osname != OS_NAME_MACOS { + cmd += " -device ide-cd,drive=ide0-cd0,bus=ide.1" + if !s.isQ35() { + cmd += ",unit=1" + } + cmd += " -drive id=ide0-cd0,media=cdrom,if=none" + } + + cdrom, _ := s.Desc.Get("cdrom") + if cdrom != nil && cdrom.Contains("path") { + cdromPath, _ := cdrom.GetString("path") + if len(cdromPath) > 0 { + if osname != OS_NAME_MACOS { + cmd += fmt.Sprintf(",file=%s", cdromPath) + } else { + cmd += " -device ide-drive,drive=MacDVD" + cmd += fmt.Sprintf(",bus=ide.%d", len(disks)) + cmd += " -drive id=MacDVD,if=none,snapshot=on" + cmd += fmt.Sprintf(",file=%s", cdromPath) + } + } + } + + for i := 0; i < len(nics); i++ { + if osname == OS_NAME_VMWARE { + nics[i].(*jsonutils.JSONDict).Set("driver", jsonutils.NewString("vmxnet3")) + } + nicCmd, err := s.getNetdevDesc(nics[i]) + if err != nil { + return "", err + } else { + cmd += nicCmd + } + cmd += s.getVnicDesc(nics[i]) + } + + cmd += fmt.Sprintf(" -pidfile %s", s.GetPidFilePath()) + extraOptions, _ := s.Desc.GetMap("extra_options") + for k, v := range extraOptions { + cmd += fmt.Sprintf(" -%s %s", k, v.String()) + } + + cmd += s.getQgaDesc() + if fileutils2.Exists("/dev/random") { + cmd += " -object rng-random,filename=/dev/random,id=rng0" + cmd += " -device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000" + } + + if jsonutils.QueryBoolean(data, "need_migrate", false) { + migratePort := s.manager.GetFreePortByBase(LIVE_MIGRATE_PORT_BASE) + s.Desc.Set("live_migrate_dest_port", jsonutils.NewInt(int64(migratePort))) + cmd += fmt.Sprintf(" -incoming tcp:0:%d", migratePort) + } else if jsonutils.QueryBoolean(s.Desc, "is_slave", false) { + cmd += fmt.Sprintf(" -incoming tcp:0:%d", + s.manager.GetFreePortByBase(LIVE_MIGRATE_PORT_BASE)) + } else if jsonutils.QueryBoolean(s.Desc, "is_master", false) { + cmd += " -S" + } + // cmd += fmt.Sprintf(" -D %s", path.Join(s.HomeDir(), "log")) + + cmd += "\"\n" + cmd += "if [ ! -z \"$STATE_FILE\" ] && [ -d \"$STATE_FILE\" ] && [ -f \"$STATE_FILE/content\" ]; then\n" + cmd += " $CMD --incoming \"exec: cat $STATE_FILE/content\"\n" + cmd += "elif [ ! -z \"$STATE_FILE\" ] && [ -f $STATE_FILE ]; then\n" + cmd += " $CMD --incoming \"exec: cat $STATE_FILE\"\n" + cmd += "else\n" + cmd += " $CMD\n" + cmd += "fi\n" + + /* + # cmd += 'sleep 1\n' + # cmd += 'PID_NUM=$(cat $PID_FILE)\n' + # cmd += 'echo -17 > /proc/$PID_NUM/oom_adj\n' + # cmd += 'echo "qemu started"\n' + */ + + return cmd, nil +} + +func (s *SKVMGuestInstance) generateStopScript(data *jsonutils.JSONDict) string { + var ( + uuid, _ = s.Desc.GetString("uuid") + nics, _ = s.Desc.GetArray("nics") + ) + + cmd := "" + cmd += fmt.Sprintf("VNC_FILE=%s\n", s.GetVncFilePath()) + cmd += fmt.Sprintf("PID_FILE=%s\n", s.GetPidFilePath()) + cmd += "if [ -f $VNC_FILE ]; then\n" + cmd += " VNC=`cat $VNC_FILE`\n" + + // TODO, replace with qmp monitor + cmd += fmt.Sprintf(" MON=$(($VNC + %d))\n", MONITOR_PORT_BASE) + cmd += " echo quit | nc -w 1 127.0.0.1 $MON > /dev/null\n" + cmd += " sleep 1\n" + cmd += " if [ -f $PID_FILE ]; then\n" + cmd += " PID=`cat $PID_FILE`\n" + cmd += " ps -p $PID > /dev/null\n" + cmd += " if [ $? -eq 0 ]; then\n" + cmd += " echo \"Kill process $PID\"\n" + cmd += " kill -9 $PID > /dev/null 2>&1\n" + cmd += " fi\n" + cmd += " echo \"Remove PID $PID_FILE\"\n" + cmd += " rm -f $PID_FILE\n" + cmd += " fi\n" + cmd += " echo \"Remove VNC $VNC_FILE\"\n" + cmd += " rm -f $VNC_FILE\n" + cmd += "fi\n" + + if options.HostOptions.HugepagesOption == "native" { + cmd += fmt.Sprintf("if [ -f /dev/hugepages/%s ]; then\n", uuid) + cmd += fmt.Sprintf(" umount /dev/hugepages/%s\n", uuid) + cmd += fmt.Sprintf(" rm -rf /dev/hugepages/%s\n", uuid) + cmd += "fi\n" + } + for _, nic := range nics { + ifname, _ := nic.GetString("ifname") + downscript := s.getNicDownScriptPath(nic) + cmd += fmt.Sprintf("%s %s\n", downscript, ifname) + } + return cmd +} + +func (s *SKVMGuestInstance) presendArpForNic(nic jsonutils.JSONObject) { + ifname, _ := nic.GetString("ifname") + ifi, err := net.InterfaceByName(ifname) + if err != nil { + log.Errorf("InterfaceByName error %s", ifname) + return + } + + cli, err := arp.Dial(ifi) + if err != nil { + log.Errorf("arp Dial error %s", err) + return + } + defer cli.Close() + + var ( + sSrcMac, _ = nic.GetString("mac") + sScrIp, _ = nic.GetString("ip") + srcIp = net.ParseIP(sScrIp) + dstMac, _ = net.ParseMAC("00:00:00:00:00:00") + dstIp = net.ParseIP("255.255.255.255") + ) + srcMac, err := net.ParseMAC(sSrcMac) + if err != nil { + log.Errorf("Send arp parse mac error: %s", err) + return + } + + pkt, err := arp.NewPacket(arp.OperationRequest, srcMac, srcIp, dstMac, dstIp) + if err != nil { + log.Errorf("New arp packet error %s", err) + return + } + if err := cli.WriteTo(pkt, ethernet.Broadcast); err != nil { + log.Errorf("Send arp packet error %s ", err) + return + } +} + +func (s *SKVMGuestInstance) StartPresendArp() { + go func() { + for i := 0; i < 5; i++ { + nics, _ := s.Desc.GetArray("nics") + for _, nic := range nics { + s.presendArpForNic(nic) + } + time.Sleep(1 * time.Second) + } + }() +} diff --git a/pkg/hostman/host_services.go b/pkg/hostman/host_services.go new file mode 100644 index 0000000000..5b2a0a610e --- /dev/null +++ b/pkg/hostman/host_services.go @@ -0,0 +1,75 @@ +package hostman + +import ( + "os" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon" + "yunion.io/x/onecloud/pkg/cloudcommon/cronman" + "yunion.io/x/onecloud/pkg/cloudcommon/service" + "yunion.io/x/onecloud/pkg/hostman/diskhandlers" + "yunion.io/x/onecloud/pkg/hostman/downloader" + "yunion.io/x/onecloud/pkg/hostman/guesthandlers" + "yunion.io/x/onecloud/pkg/hostman/guestman" + "yunion.io/x/onecloud/pkg/hostman/hostinfo" + "yunion.io/x/onecloud/pkg/hostman/hostmetrics" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" +) + +type SHostService struct { + service.SServiceBase +} + +func (host *SHostService) StartService() { + cloudcommon.ParseOptions(&options.HostOptions, os.Args, "host.conf", "host") + options.HostOptions.EnableRbac = false // disable rbac + + app := cloudcommon.InitApp(&options.HostOptions.CommonOptions, false) + hostInstance := hostinfo.Instance() + if err := hostInstance.Init(); err != nil { + log.Fatalf(err.Error()) + } + + if err := storageman.Init(hostInstance); err != nil { + log.Fatalf(err.Error()) + } + + guestman.Init(hostInstance, options.HostOptions.ServersPath) + cloudcommon.InitAuth(&options.HostOptions.CommonOptions, func() { + log.Infof("Auth complete!!") + // ??? Why wait 5 seconds + + hostInstance.StartRegister(2, func() { + guestman.GetGuestManager().Bootstrap() + // hostmetrics after guestmanager bootstrap + hostmetrics.Init() + hostmetrics.Start() + }) + }) + host.initHandlers(app) + <-hostinfo.Instance().IsRegistered // wait host and guest init + + cronManager := cronman.GetCronJobManager(false) + cronManager.AddJob2( + "CleanRecycleDiskFiles", 1, 3, 0, 0, storageman.CleanRecycleDiskfiles, false) + + cloudcommon.ServeForeverWithCleanup(app, &options.HostOptions.CommonOptions, func() { + hostinfo.Stop() + storageman.Stop() + hostmetrics.Stop() + guestman.Stop() + hostutils.GetWorkManager().Stop() + }) +} + +func (host *SHostService) initHandlers(app *appsrv.Application) { + guesthandlers.AddGuestTaskHandler("", app) + storageman.AddStorageHandler("", app) + diskhandlers.AddDiskHandler("", app) + downloader.AddDownloadHandler("", app) + addKubeAgentHandler("", app) +} diff --git a/pkg/hostman/hostinfo/doc.go b/pkg/hostman/hostinfo/doc.go new file mode 100644 index 0000000000..bc9428a5a8 --- /dev/null +++ b/pkg/hostman/hostinfo/doc.go @@ -0,0 +1 @@ +package hostinfo // import "yunion.io/x/onecloud/pkg/hostman/hostinfo" diff --git a/pkg/hostman/hostinfo/hostbridge/doc.go b/pkg/hostman/hostinfo/hostbridge/doc.go new file mode 100644 index 0000000000..ccc4961d70 --- /dev/null +++ b/pkg/hostman/hostinfo/hostbridge/doc.go @@ -0,0 +1 @@ +package hostbridge // import "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge" diff --git a/pkg/hostman/hostinfo/hostbridge/hostbridge.go b/pkg/hostman/hostinfo/hostbridge/hostbridge.go new file mode 100644 index 0000000000..a2aded7584 --- /dev/null +++ b/pkg/hostman/hostinfo/hostbridge/hostbridge.go @@ -0,0 +1,569 @@ +package hostbridge + +import ( + "fmt" + "net" + "os" + "strings" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/system_service" + "yunion.io/x/onecloud/pkg/util/bwutils" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/netutils2" + "yunion.io/x/onecloud/pkg/util/ovsutils" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +type IBridgeDriver interface { + ConfirmToConfig(bool, []string) (bool, error) + Setup() error + Exists() bool + Interfaces() []string + + GetMac() string + GenerateIfupScripts(scriptPath string, nic jsonutils.JSONObject) error + GenerateIfdownScripts(scriptPath string, nic jsonutils.JSONObject) error + RegisterHostlocalServer(mac, ip string) error + WarmupConfig() error + CleanupConfig() +} + +type SBaseBridgeDriver struct { + bridge *netutils2.SNetInterface + ip string + inter *netutils2.SNetInterface +} + +func NewBaseBridgeDriver(bridge, inter, ip string) (*SBaseBridgeDriver, error) { + var bd = new(SBaseBridgeDriver) + bd.bridge = netutils2.NewNetInterface(bridge) + if len(inter) > 0 { + bd.inter = netutils2.NewNetInterface(inter) + if bd.inter == nil { + return nil, fmt.Errorf("%s not exists", inter) + } + bd.ip = ip + bd.inter.DisableGso() + } else if len(ip) > 0 { + return nil, fmt.Errorf("A bridge without interface must have no IP") + } + return bd, nil +} + +func (d *SBaseBridgeDriver) GetMac() string { + if len(d.bridge.Mac) == 0 { + d.bridge.FetchConfig() + } + return d.bridge.Mac +} + +func (d *SBaseBridgeDriver) BringupInterface() error { + var infs = []*netutils2.SNetInterface{d.bridge} + if d.inter != nil { + infs = append(infs, d.inter) + } + for _, inf := range infs { + cmd := []string{"ifconfig", inf.String(), "up"} + if options.HostOptions.TunnelPaddingBytes > 0 { + cmd = append(cmd, "mtu", fmt.Sprintf("%d", options.HostOptions.TunnelPaddingBytes)) + } + if _, err := procutils.NewCommand(cmd[0], cmd[1:]...).Run(); err != nil { + return err + } + } + return nil +} + +func (d *SBaseBridgeDriver) ConfirmToConfig(exists bool, infs []string) (bool, error) { + if exists { + d.bridge.FetchConfig() + if len(d.ip) > 0 { + if len(d.bridge.Addr) == 0 { + if len(d.inter.Addr) == 0 { + return false, fmt.Errorf("Neither %s nor %s owner address %s", + d.inter, d.bridge, d.ip) + } + if d.inter.Addr != d.ip { + return false, fmt.Errorf("%s!=%s, %s not same as config", + d.ip, d.inter.Addr, d.inter) + } + log.Infof("Bridge address is not configured") + return false, nil + } + if d.bridge.Addr != d.ip { + return false, fmt.Errorf("%s IP %s!=%s, mismatch", d.bridge, d.bridge.Addr, d.ip) + } + } else { + if d.inter != nil && len(d.inter.Addr) > 0 { + return false, fmt.Errorf("%s should have no address", d.inter) + } + if len(d.bridge.Addr) == 0 { + return false, nil + } + if !d.bridge.IsSecretInterface() { + return false, fmt.Errorf("%s should have address in 169.254.0.0/16", d.bridge) + } + } + if d.inter != nil && !utils.IsInStringArray(d.inter.String(), infs) { + log.Infof("Interface %s not in bridge...", d.inter) + return false, nil + } + if err := d.BringupInterface(); err != nil { + log.Errorln(err) + return false, err + } + return true, nil + } else { + if d.bridge.FetchInter() != nil { + return false, fmt.Errorf("Bridge %s exists, but not created by this driver????", d.bridge) + } + if len(d.ip) > 0 && (d.inter == nil || len(d.inter.Addr) == 0) { + return false, fmt.Errorf("Interface %s not configured", d.inter) + } + return false, nil + } +} + +func (d *SBaseBridgeDriver) SetupAddresses(mask net.IPMask) error { + var addr string + if len(d.ip) > 0 { + addr, mask = netutils2.GetSecretInterfaceAddress() + } else { + addr = d.ip + } + cmd := []string{"ifconfig", d.bridge.String(), addr, "netmask", netutils2.NetBytes2Mask(mask)} + if options.HostOptions.TunnelPaddingBytes > 0 { + cmd = append(cmd, "mtu", fmt.Sprintf("%d", options.HostOptions.TunnelPaddingBytes+1500)) + } + if _, err := procutils.NewCommand(cmd[0], cmd[1:]...).Run(); err != nil { + log.Errorln(err) + return fmt.Errorf("Failed to bring up bridge %s", d.bridge) + } + if d.inter != nil { + if _, err := procutils.NewCommand("ifconfig", d.inter.String(), "0", "up").Run(); err != nil { + log.Errorln(err) + return fmt.Errorf("Failed to bring up interface %s", d.inter) + } + } + return nil +} + +func (d *SBaseBridgeDriver) SetupSlaveAddresses(slaveAddrs [][]string) error { + for _, slaveAddr := range slaveAddrs { + cmd := []string{"ip", "address", "del", + fmt.Sprintf("%s/%s", slaveAddr[0], slaveAddr[1]), "dev", d.inter.String()} + if _, err := procutils.NewCommand(cmd[0], cmd[1:]...).Run(); err != nil { + log.Errorln("Failed to remove slave address from interface %s: %s", d.inter, err) + } + + cmd = []string{"ip", "address", "add", + fmt.Sprintf("%s/%s", slaveAddr[0], slaveAddr[1]), "dev", d.bridge.String()} + if _, err := procutils.NewCommand(cmd[0], cmd[1:]...).Run(); err != nil { + return fmt.Errorf("Failed to remove slave address from interface %s: %s", d.bridge, err) + } + } + return nil +} + +func (d *SBaseBridgeDriver) SetupRoutes(routes [][]string) error { + for _, r := range routes { + var cmd []string + if r[2] == "0.0.0.0" { + cmd = []string{"route", "add", "default", "gw", r[1], "dev", d.bridge.String()} + } else { + cmd = []string{"route", "add", "-net", r[0], "netmask", r[2], "gw", r[1], "dev", d.bridge.String()} + } + if _, err := procutils.NewCommand(cmd[0], cmd[1:]...).Run(); err != nil { + log.Errorln(err) + return fmt.Errorf("Failed to add slave address to bridge %s", d.bridge) + } + } + return nil +} + +type SOVSBridgeDriver struct { + SBaseBridgeDriver +} + +func (o *SOVSBridgeDriver) CleanupConfig() { + ovsutils.CleanAllHiddenPorts() + // if enableopenflowcontroller ... +} + +func (o *SOVSBridgeDriver) Exists() bool { + data, err := procutils.NewCommand("ovs-vsctl", "list-br").Run() + if err != nil { + log.Errorln(err) + return false + } + for _, d := range strings.Split(string(data), "\n") { + if strings.TrimSpace(d) == o.bridge.String() { + return true + } + } + return false +} + +func (o *SOVSBridgeDriver) Interfaces() []string { + data, err := procutils.NewCommand("ovs-vsctl", "list-ifaces", o.bridge.String()).Run() + if err != nil { + log.Errorln(err) + return nil + } + + var infs = make([]string, 0) + for _, d := range strings.Split(string(data), "\n") { + if len(strings.TrimSpace(d)) > 0 { + infs = append(infs, strings.TrimSpace(d)) + } + } + return infs +} + +func (o *SOVSBridgeDriver) Setup() error { + var routes [][]string + var slaveAddrs [][]string + if o.inter != nil && len(o.inter.Addr) > 0 { + routes = o.inter.GetRoutes(true) + slaveAddrs = o.inter.GetSlaveAddresses() + } + if !o.Exists() { + if err := o.SetupBridgeDev(); err != nil { + return err + } + } + + if o.inter != nil && !utils.IsInStringArray(o.inter.String(), o.Interfaces()) { + if err := o.SetupInterface(); err != nil { + return err + } + } + if len(o.bridge.Addr) == 0 { + if len(o.ip) > 0 { + if err := o.SetupAddresses(o.inter.Mask); err != nil { + return err + } + if len(slaveAddrs) > 0 { + if err := o.SetupSlaveAddresses(slaveAddrs); err != nil { + return err + } + } + if len(routes) > 0 { + if err := o.SetupRoutes(routes); err != nil { + return err + } + } + } else { + if err := o.SetupAddresses(nil); err != nil { + return err + } + } + } + + return o.BringupInterface() +} + +func (o *SOVSBridgeDriver) SetupInterface() error { + if o.inter != nil && !utils.IsInStringArray(o.inter.String(), o.Interfaces()) { + output, err := procutils.NewCommand("ovs-vsctl", "--", "--may-exist", + "add-port", o.bridge.String(), o.inter.String()).Run() + if err != nil { + return fmt.Errorf("Failed to add interface %s", output) + } + } + return nil +} + +func (o *SOVSBridgeDriver) SetupBridgeDev() error { + if !o.Exists() { + _, err := procutils.NewCommand("ovs-vsctl", "--", "--may-exist", "add-br", o.bridge.String()).Run() + return err + } + return nil +} + +func (o *SOVSBridgeDriver) GenerateIfdownScripts(scriptPath string, nic jsonutils.JSONObject) error { + script, err := o.getDownScripts(nic) + if err != nil { + log.Errorln(err) + return err + } + return o.saveFileExecutable(scriptPath, script) +} + +func (o *SOVSBridgeDriver) GenerateIfupScripts(scriptPath string, nic jsonutils.JSONObject) error { + script, err := o.getUpScripts(nic) + if err != nil { + log.Errorln(err) + return err + } + return o.saveFileExecutable(scriptPath, script) +} + +func (o *SOVSBridgeDriver) saveFileExecutable(scriptPath, script string) error { + if err := fileutils2.FilePutContents(scriptPath, script, false); err != nil { + return err + } + return os.Chmod(scriptPath, syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR) +} + +func (o *SOVSBridgeDriver) getUpScripts(nic jsonutils.JSONObject) (string, error) { + var ( + bridge, _ = nic.GetString("bridge") + ifname, _ = nic.GetString("ifname") + ip, _ = nic.GetString("ip") + mac, _ = nic.GetString("mac") + vlan, _ = nic.Int("vlan") + ) + + s := "#!/bin/bash\n\n" + s += fmt.Sprintf("SWITCH='%s'\n", bridge) + s += fmt.Sprintf("IF='%s'\n", ifname) + s += fmt.Sprintf("IP='%s'\n", ip) + s += fmt.Sprintf("MAC='%s'\n", mac) + s += fmt.Sprintf("VLAN_ID=%d\n", vlan) + limit, burst, err := bwutils.GetOvsBwValues(nic) + if err != nil { + return "", err + } + s += fmt.Sprintf("LIMIT=%d\n", limit) + s += fmt.Sprintf("BURST=%d\n", burst) + bwDownload, err := bwutils.GetDownloadBwValue(nic, options.HostOptions.BwDownloadBandwidth) + if err != nil { + return "", err + } + s += fmt.Sprintf("LIMIT_DOWNLOAD='%dmbit'\n", bwDownload) + if options.HostOptions.TunnelPaddingBytes > 0 { + s += fmt.Sprintf("/sbin/ifconfig $IF mtu %d\n", + 1500+options.HostOptions.TunnelPaddingBytes) + } + s += "/sbin/ifconfig $IF 0.0.0.0 up\n" + s += "ovs-vsctl list-ifaces $SWITCH | grep -w $IF > /dev/null 2>&1\n" + s += "if [ $? -eq '0' ]; then\n" + s += " ovs-vsctl del-port $SWITCH $IF\n" + s += "fi\n" + s += "if [ \"$VLAN_ID\" -ne \"1\" ]; then\n" + s += " TAG=\"tag=$VLAN_ID\"\n" + s += "fi\n" + s += "ovs-vsctl add-port $SWITCH $IF $TAG\n" + s += "PORT=$(ovs-ofctl show $SWITCH | grep -w $IF)\n" + s += "PORT=$(echo $PORT | awk 'BEGIN{FS=\"(\"}{print $1}')\n" + s += "OFCTL=$(ovs-vsctl get-controller $SWITCH)\n" + s += "if [ -z \"$OFCTL\" ]; then\n" + s += " ovs-vsctl set Interface $IF ingress_policing_rate=$LIMIT\n" + s += " ovs-vsctl set Interface $IF ingress_policing_burst=$BURST\n" + for _, r := range o.GetOfRules(nic) { + s += " " + o.AddFlow(r.cond, r.priority, r.actions) + } + s += "fi\n" + s += "if [ $LIMIT_DOWNLOAD != \"0mbit\" ]; then\n" + s += " tc qdisc del dev $IF root 2>/dev/null\n" + s += " tc qdisc add dev $IF root handle 1: htb default 10\n" + s += " tc class add dev $IF parent 1: classid 1:1 htb " + + "rate $LIMIT_DOWNLOAD ceil $LIMIT_DOWNLOAD\n" + s += " tc class add dev $IF parent 1:1 classid 1:10 htb " + + "rate $LIMIT_DOWNLOAD ceil $LIMIT_DOWNLOAD\n" + s += "fi\n" + return s, nil +} + +func (o *SOVSBridgeDriver) getDownScripts(nic jsonutils.JSONObject) (string, error) { + var ( + bridge, _ = nic.GetString("bridge") + ifname, _ = nic.GetString("ifname") + ip, _ = nic.GetString("ip") + mac, _ = nic.GetString("mac") + vlan, _ = nic.Int("vlan") + ) + + s := "#!/bin/bash\n\n" + s += fmt.Sprintf("SWITCH='%s'\n", bridge) + s += fmt.Sprintf("IF='%s'\n", ifname) + s += fmt.Sprintf("IP='%s'\n", ip) + s += fmt.Sprintf("MAC='%s'\n", mac) + s += fmt.Sprintf("VLAN_ID=%d\n", vlan) + s += "PORT=$(ovs-ofctl show $SWITCH | grep -w $IF)\n" + s += "if [ $? -ne '0' ]; then\n" + s += " exit 0\n" + s += "fi\n" + s += "OFCTL=$(ovs-vsctl get-controller $SWITCH)\n" + s += "PORT=$(echo $PORT | awk 'BEGIN{FS=\"(\"}{print $1}')\n" + s += "if [ -z \"$OFCTL\" ]; then\n" + for _, r := range o.GetOfRules(nic) { + s += " " + o.DelFlow(r.cond) + } + s += "fi\n" + s += "/sbin/ifconfig $IF 0.0.0.0 down\n" + s += "ovs-vsctl -- --if-exists del-port $SWITCH $IF\n" + return s, nil +} + +type SRule struct { + priority int + cond string + actions string +} + +func (o *SOVSBridgeDriver) AddFlow(cond string, priority int, actions string) string { + s := "" + s += fmt.Sprintf("ovs-ofctl add-flow $SWITCH \"%s", cond) + s += fmt.Sprintf(" priority=%d", priority) + s += fmt.Sprintf(" actions=%s\"\n", actions) + return s +} + +func (o *SOVSBridgeDriver) DoAddFlow(cond string, pri int, actions, swt string) error { + _, err := procutils.NewCommand("ovs-ofctl", "add-flow", swt, + fmt.Sprintf("%s priority=%d actions=%s", cond, pri, actions)).Run() + return err +} + +func (o *SOVSBridgeDriver) DelFlow(cond string) string { + return fmt.Sprintf("ovs-ofctl del-flows $SWITCH \"%s\"\n", cond) +} + +func (o *SOVSBridgeDriver) GetOfRules(nic jsonutils.JSONObject) []SRule { + rules := []SRule{} + metadataPort := o.GetMetadataServerPort() + rules = append(rules, + SRule{9000, fmt.Sprintf("table=0 in_port=local tcp nw_dst=$IP tp_src=%d", metadataPort), + "mod_nw_src=169.254.169.254,mod_tp_src:80,output:$PORT"}, + SRule{9500, "table=0 in_port=$PORT udp tp_src=68 tp_dst=67", "local"}, + SRule{8000, "table=0 in_port=$PORT", "resubmit(,1)"}, + ) + if vlan, _ := nic.Int("vlan"); vlan != 1 { + rules = append(rules, + SRule{4901, "table=1 dl_dst=$MAC,dl_vlan=$VLAN_ID", "strip_vlan,output:$PORT"}) + } + rules = append(rules, + SRule{4900, "table=1 dl_dst=$MAC", "output:$PORT"}) + return rules +} + +func (o *SOVSBridgeDriver) GetMetadataServerPort() int { + return options.HostOptions.Port +} + +func (o *SOVSBridgeDriver) RegisterHostlocalServer(mac, ip string) error { + if !options.HostOptions.EnableOpenflowController { + metadataPort := o.GetMetadataServerPort() + if err := o.DoAddFlow("table=0 ipv6", 20000, "drop", o.bridge.String()); err != nil { + log.Errorln(err) + return err + } + if err := o.DoAddFlow("table=0 tcp nw_dst=169.254.169.254 tp_dst=80", 10000, + fmt.Sprintf("mod_dl_dst:%s,mod_nw_dst:%s,mod_tp_dst:%d,local", + mac, ip, metadataPort), + o.bridge.String()); err != nil { + log.Errorln(err) + return err + } + log.Infof("OVS: metadata server %s:%d", ip, metadataPort) + + k8sCidr := options.HostOptions.K8sClusterCidr + if len(k8sCidr) > 0 { + addr, mask, err := netutils2.PrefixSplit(k8sCidr) + if err != nil { + log.Errorln(err) + return err + } + k8sCidr = fmt.Sprintf("%s/%d", addr, mask) + log.Infof("OVS: Kubernetes cluster IP range: %s", k8sCidr) + err = o.DoAddFlow(fmt.Sprintf("table=0 ip,nw_dst=%s", k8sCidr), + 10050, fmt.Sprintf("mod_dl_dst:%s,local", mac), o.bridge.String()) + if err != nil { + log.Errorln(err) + return err + } + } + + err := o.DoAddFlow("table=0", 0, "resubmit(,1)", o.bridge.String()) + if err != nil { + log.Errorln(err) + return err + } + err = o.DoAddFlow("table=1", 0, "normal", o.bridge.String()) + if err != nil { + log.Errorln(err) + return err + } + } + return nil +} + +func (o *SOVSBridgeDriver) ovsSetParams(params map[string]map[string]string) { + for tbl, tblval := range params { + for k, v := range tblval { + procutils.NewCommand("ovs-vsctl", "set", tbl, o.bridge.String(), + fmt.Sprintf("%s=%s", k, v)).Run() + } + } +} + +func (o *SOVSBridgeDriver) WarmupConfig() error { + // if options.OvsSflowBridges ... + if options.HostOptions.EnableOpenflowController { + // ... + } else { + params := map[string]map[string]string{ + "bridge": { + "stp_enable": "false", + "fail_mode": "standalone", + "other-config:flow-eviction-threshold": "2500", + }, + } + o.ovsSetParams(params) + } + return nil +} + +func OVSPrepare() error { + ovs := system_service.GetService("openvswitch") + if !ovs.IsInstalled() { + return fmt.Errorf("Service openvswitch not installed!") + } + if !ovs.IsActive() { + return ovs.Start(false) + } + return nil +} + +func CleanOvsBridge() { + ovsutils.CleanAllHiddenPorts() +} + +func NewOVSBridgeDriver(bridge, inter, ip string) (*SOVSBridgeDriver, error) { + base, err := NewBaseBridgeDriver(bridge, inter, ip) + if err != nil { + return nil, err + } + return &SOVSBridgeDriver{*base}, nil +} + +func NewDriver(bridgeDriver, bridge, inter, ip string) (IBridgeDriver, error) { + if bridgeDriver == "openvswitch" { + return NewOVSBridgeDriver(bridge, inter, ip) + } else { + return nil, fmt.Errorf("Not Implentment") + } +} + +func Prepare(bridgeDriver string) error { + if bridgeDriver == "openvswitch" { + return OVSPrepare() + } else { + return fmt.Errorf("Not Implentment") + } +} + +func CleanDeletedPorts() { + if options.HostOptions.BridgeDriver == "openvswitch" { + CleanOvsBridge() + } +} diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go b/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go new file mode 100644 index 0000000000..d23358930c --- /dev/null +++ b/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go @@ -0,0 +1,119 @@ +package hostdhcp + +import ( + "net" + "strconv" + "sync" + "time" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/util/dhcp" +) + +const DEFAULT_DHCP_RELAY_PORT = 68 + +type recvFunc func(pkt *dhcp.Packet) + +type SRelayCache struct { + mac net.HardwareAddr + srcPort int + // dstPort int + + timer time.Time +} + +type SDHCPRelay struct { + server *dhcp.DHCPServer + OnRecv recvFunc + conn *dhcp.Conn + + guestDHCPConn *dhcp.Conn + + srcaddr string + + destaddr net.IP + destport int + + cache sync.Map +} + +func NewDHCPRelay(guestDHCPConn *dhcp.Conn, addrs []string) (*SDHCPRelay, error) { + relay := new(SDHCPRelay) + relay.guestDHCPConn = guestDHCPConn + addr := addrs[0] + port, err := strconv.Atoi(addrs[1]) + if err != nil { + log.Errorln(err) + return nil, err + } + relay.destaddr = net.ParseIP(addr) + relay.destport = port + relay.cache = sync.Map{} + + return relay, nil +} + +func (r *SDHCPRelay) Start() { + log.Infof("DHCPRelay starting ...") + go func() { + err := r.server.ListenAndServe(r) + if err != nil { + log.Errorf("DHCP Relay error %s", err) + } + }() +} + +func (r *SDHCPRelay) Setup(addr string) error { + var err error + r.srcaddr = addr + log.Infof("DHCP Relay Bind addr %s port %d", r.srcaddr, DEFAULT_DHCP_RELAY_PORT) + r.server, r.conn, err = dhcp.NewDHCPServer2(r.srcaddr, DEFAULT_DHCP_RELAY_PORT) + if err != nil { + log.Errorln(err) + return err + } + r.Start() + return nil +} + +func (r *SDHCPRelay) ServeDHCP(pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, error) { + log.Infof("Receive DHCP Relay Reply TO %s", pkt.CHAddr()) + v, ok := r.cache.Load(pkt.TransactionID()) + if ok { + r.cache.Delete(pkt.TransactionID()) + val := v.(*SRelayCache) + udpAddr := &net.UDPAddr{ + IP: pkt.CIAddr(), + Port: val.srcPort, + } + if err := r.guestDHCPConn.SendDHCP(pkt, udpAddr, intf); err != nil { + log.Errorln(err) + } + } + return nil, nil +} + +func (r *SDHCPRelay) Relay(pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, error) { + log.Infof("Receive DHCP Relay Rquest FROM %s", pkt.CHAddr()) + + // clean cache first + var now = time.Now().Add(time.Second * -30) + r.cache.Range(func(key, value interface{}) bool { + v := value.(*SRelayCache) + if v.timer.Before(now) { + r.cache.Delete(key) + } + return true + }) + + // cache pkt info + r.cache.Store(pkt.TransactionID(), &SRelayCache{ + mac: pkt.CHAddr(), + srcPort: addr.Port, + timer: time.Now(), + }) + + pkt.SetGIAddr(r.destaddr) + err := r.conn.SendDHCP(pkt, &net.UDPAddr{IP: r.destaddr, Port: r.destport}, intf) + return nil, err +} diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go new file mode 100644 index 0000000000..34fb6d8e3f --- /dev/null +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go @@ -0,0 +1,151 @@ +package hostdhcp + +import ( + "net" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/hostman/guestman" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/dhcp" + "yunion.io/x/onecloud/pkg/util/netutils2" +) + +var DEFAULT_DHCP_BIND_ADDR = "0.0.0.0" + +type SGuestDHCPServer struct { + server *dhcp.DHCPServer + relay *SDHCPRelay + conn *dhcp.Conn + + iface string +} + +func NewGuestDHCPServer(iface string, relay []string) (*SGuestDHCPServer, error) { + var ( + err error + guestdhcp = new(SGuestDHCPServer) + ) + + guestdhcp.server, guestdhcp.conn, err = dhcp.NewDHCPServer2(DEFAULT_DHCP_BIND_ADDR, options.HostOptions.DhcpServerPort) + if err != nil { + return nil, err + } + + if len(relay) == 2 { + guestdhcp.relay, err = NewDHCPRelay(guestdhcp.conn, relay) + if err != nil { + return nil, err + } + } + + guestdhcp.iface = iface + return guestdhcp, nil +} + +func (s *SGuestDHCPServer) Start() { + log.Infof("SGuestDHCPServer starting ...") + go func() { + err := s.server.ListenAndServe(s) + if err != nil { + log.Errorf("DHCP serve error: %s", err) + } + }() +} + +func (s *SGuestDHCPServer) RelaySetup(addr string) error { + if s.relay != nil { + return s.relay.Setup(addr) + } + return nil +} + +func (s *SGuestDHCPServer) getGuestConfig(guestDesc, guestNic jsonutils.JSONObject) *dhcp.ResponseConfig { + var nicdesc = new(types.SServerNic) + if err := guestNic.Unmarshal(nicdesc); err != nil { + log.Errorln(err) + return nil + } + + var conf = new(dhcp.ResponseConfig) + nicIp := nicdesc.Ip + v4Ip, _ := netutils.NewIPV4Addr(nicIp) + conf.ClientIP = net.ParseIP(nicdesc.Ip) + + masklen := nicdesc.Masklen + conf.ServerIP = net.ParseIP(v4Ip.NetAddr(int8(masklen)).String()) + conf.SubnetMask = net.ParseIP(netutils2.Netlen2Mask(masklen)) + conf.BroadcastAddr = v4Ip.BroadcastAddr(int8(masklen)).ToBytes() + conf.Hostname, _ = guestDesc.GetString("name") + conf.Domain = nicdesc.Domain + + // get main ip + guestNics, _ := guestDesc.GetArray("nics") + manNic, err := netutils2.GetMainNic(guestNics) + if err != nil { + log.Errorln(err) + return nil + } + mainIp, _ := manNic.GetString("ip") + + var route = [][]string{} + if len(nicdesc.Gateway) > 0 && mainIp == nicIp { + conf.Gateway = net.ParseIP(nicdesc.Gateway) + + osName, _ := guestDesc.GetString("os_name") + if len(osName) == 0 { + osName = "Linux" + } + if !strings.HasPrefix(strings.ToLower(osName), "win") { + route = append(route, []string{"0.0.0.0/0", nicdesc.Gateway}) + } + route = append(route, []string{"169.254.169.254/32", nicdesc.Gateway}) + } + netutils2.AddNicRoutes( + &route, nicdesc, mainIp, len(guestNics), options.HostOptions.PrivatePrefixes) + conf.Routes = route + + if len(nicdesc.Dns) > 0 { + conf.DNSServer = net.ParseIP(nicdesc.Dns) + } + conf.OsName, _ = guestDesc.GetString("os_name") + conf.LeaseTime = time.Duration(options.HostOptions.DhcpLeaseTime) * time.Second + conf.RenewalTime = time.Duration(options.HostOptions.DhcpRenewalTime) * time.Second + return conf +} + +func (s *SGuestDHCPServer) getConfig(pkt dhcp.Packet) *dhcp.ResponseConfig { + var ( + guestmananger = guestman.GetGuestManager() + mac = pkt.CHAddr().String() + ip, port = "", "" + isCandidate = false + ) + guestDesc, guestNic := guestmananger.GetGuestNicDesc(mac, ip, port, s.iface, isCandidate) + if guestNic == nil { + guestDesc, guestNic = guestmananger.GetGuestNicDesc(mac, ip, port, s.iface, !isCandidate) + } + if guestNic != nil && !jsonutils.QueryBoolean(guestNic, "virtual", false) { + return s.getGuestConfig(guestDesc, guestNic) + } + return nil +} + +func (s *SGuestDHCPServer) ServeDHCP(pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, error) { + var conf = s.getConfig(pkt) + if conf != nil { + log.Infof("Make DHCP Reply %s TO %s", conf.ClientIP, pkt.CHAddr()) + + // Guest request ip + return dhcp.MakeReplyPacket(pkt, conf) + } else if s.relay != nil { + // Host agent as dhcp relay, relay to baremetal + return s.relay.Relay(pkt, addr, intf) + } + return nil, nil +} diff --git a/pkg/hostman/hostinfo/hostdhcp/doc.go b/pkg/hostman/hostinfo/hostdhcp/doc.go new file mode 100644 index 0000000000..8aba49709c --- /dev/null +++ b/pkg/hostman/hostinfo/hostdhcp/doc.go @@ -0,0 +1 @@ +package hostdhcp // import "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostdhcp" diff --git a/pkg/hostman/hostinfo/hostinfo.go b/pkg/hostman/hostinfo/hostinfo.go new file mode 100644 index 0000000000..fdc3b864dc --- /dev/null +++ b/pkg/hostman/hostinfo/hostinfo.go @@ -0,0 +1,1427 @@ +package hostinfo + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path" + "reflect" + "regexp" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + bare2 "yunion.io/x/onecloud/pkg/baremetal" + "yunion.io/x/onecloud/pkg/cloudcommon/storagetypes" + bare1 "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/isolated_device" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman" + "yunion.io/x/onecloud/pkg/hostman/system_service" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/cgrouputils" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/netutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemutils" + "yunion.io/x/onecloud/pkg/util/sysutils" + "yunion.io/x/onecloud/pkg/util/timeutils2" + "yunion.io/x/onecloud/pkg/util/winutils" +) + +var ( + KVM_MODULE_INTEL = "kvm-intel" + KVM_MODULE_AMD = "kvm-amd" + KVM_MODULE_UNSUPPORT = "unsupport" + + HOST_NEST_UNSUPPORT = "0" + HOST_NEST_SUPPORT = "1" + HOST_NEST_ENABLE = "3" +) + +type SHostInfo struct { + isRegistered bool + IsRegistered chan struct{} + registerCallback func() + stopped bool + + saved bool + pinger *SHostPingTask + + kvmModuleSupport string + nestStatus string + + Cpu *SCPUInfo + Mem *SMemory + sysinfo *SSysInfo + + IsolatedDeviceMan *isolated_device.IsolatedDeviceManager + + MasterNic *netutils2.SNetInterface + Nics []*SNIC + + HostId string + Zone string + ZoneId string + Cloudregion string + CloudregionId string + ZoneManagerUri string + + FullName string +} + +func (h *SHostInfo) GetIsolatedDeviceManager() *isolated_device.IsolatedDeviceManager { + return h.IsolatedDeviceMan +} + +func (h *SHostInfo) GetBridgeDev(bridge string) hostbridge.IBridgeDriver { + for _, n := range h.Nics { + if bridge == n.Bridge { + return n.BridgeDev + } + } + return nil +} + +func (h *SHostInfo) GetHostId() string { + return h.HostId +} + +func (h *SHostInfo) GetZone() string { + return h.Zone +} + +func (h *SHostInfo) GetMediumType() string { + if h.sysinfo != nil { + return h.sysinfo.StorageType + } + return "" +} + +func (h *SHostInfo) IsKvmSupport() bool { + if h.kvmModuleSupport == KVM_MODULE_UNSUPPORT { + return false + } + return true +} + +func (h *SHostInfo) IsNestedVirtualization() bool { + return utils.IsInStringArray("hypervisor", h.Cpu.cpuFeatures) +} + +func (h *SHostInfo) Init() error { + if err := h.prepareEnv(); err != nil { + return err + } + log.Infof("Start parseConfig") + if err := h.parseConfig(); err != nil { + return err + } + log.Infof("Start detectHostInfo") + if err := h.detectHostInfo(); err != nil { + return err + } + return nil +} + +func (h *SHostInfo) parseConfig() error { + if h.GetMemory() < 64 { // MB + return fmt.Errorf("Not enough memory!") + } + if len(options.HostOptions.ListenInterface) > 0 { + h.MasterNic = netutils2.NewNetInterface(options.HostOptions.ListenInterface) + } else { + h.MasterNic = nil + } + for _, n := range options.HostOptions.Networks { + nic, err := NewNIC(n) + if err != nil { + return err + } + h.Nics = append(h.Nics, nic) + } + for i := 0; i < len(h.Nics); i++ { + if err := h.Nics[i].SetupDhcpRelay(); err != nil { + return err + } + } + + if man, err := isolated_device.NewManager(h); err != nil { + return fmt.Errorf("NewIsolatedManager: %v", err) + } else { + h.IsolatedDeviceMan = man + } + + return nil +} + +func (h *SHostInfo) prepareEnv() error { + if err := h.fixPathEnv(); err != nil { + return err + } + if options.HostOptions.ReportInterval > 300 { + return fmt.Errorf("Option report_interval must no longer than 5 min") + } + + _, err := procutils.NewCommand("mkdir", "-p", options.HostOptions.ServersPath).Run() + if err != nil { + return fmt.Errorf("Failed to create path %s", options.HostOptions.ServersPath) + } + + _, err = procutils.NewCommand(qemutils.GetQemu(""), "-version").Run() + if err != nil { + return fmt.Errorf("Qemu/Kvm not installed") + } + + if !fileutils2.Exists("/sbin/ethtool") { + return fmt.Errorf("Ethtool not installed") + } + + ioParams := make(map[string]string, 0) + if options.HostOptions.BlockIoScheduler == "deadline" { + ioParams["queue/scheduler"] = "deadline" + } else { + ioParams["queue/scheduler"] = "cfq" + ioParams["queue/iosched/group_isolation"] = "1" + ioParams["queue/iosched/slice_idle"] = "0" + ioParams["queue/iosched/group_idle"] = "0" + ioParams["queue/iosched/quantum"] = "32" + } + fileutils2.ChangeAllBlkdevsParams(ioParams) + _, err = procutils.NewCommand("modprobe", "tun").Run() + if err != nil { + return fmt.Errorf("Failed to activate tun/tap device") + } + output, err := procutils.NewCommand("modprobe", "vhost_net").Run() + if err != nil { + log.Errorf("modprobe error: %s", output) + } + if !cgrouputils.Init() { + return fmt.Errorf("Cannot initialize control group subsystem") + } + + output, err = procutils.NewCommand("rmmod", "nbd").Run() + if err != nil { + log.Errorf("rmmod error: %s", output) + } + output, err = procutils.NewCommand("modprobe", "nbd", "max_part=16").Run() + if err != nil { + log.Errorf("Failed to activate nbd device: %s", output) + } + + if !winutils.CheckTool(options.HostOptions.ChntpwPath) { + return fmt.Errorf("Failed to find chntpw tool") + } + + if err := hostbridge.Prepare(options.HostOptions.BridgeDriver); err != nil { + log.Errorln(err) + return err + } + + err = h.resetIptables() + if err != nil { + return err + } + + if options.HostOptions.EnableKsm { + h.EnableKsm(900) + } else { + h.DisableKsm() + } + + switch options.HostOptions.HugepagesOption { + case "disable": + h.DisableHugepages() + case "native": + if err := h.EnableNativeHugepages(); err != nil { + return err + } + case "transparent": + h.EnableTransparentHugepages() + default: + return fmt.Errorf("Invalid hugepages option") + } + for i := 0; i < 16; i++ { + nbdBdi := fmt.Sprintf("/sys/block/nbd%d/bdi/", i) + h.setSysConfig(nbdBdi+"max_ratio", "0") + h.setSysConfig(nbdBdi+"min_ratio", "0") + } + h.PreventArpFlux() + h.TuneSystem() + return nil +} + +func (h *SHostInfo) detectHostInfo() error { + output, err := procutils.NewCommand("dmidecode", "-t", "1").Run() + if err != nil { + return err + } + + sysinfo, err := sysutils.ParseDMISysinfo(strings.Split(string(output), "\n")) + if err != nil { + return err + } + h.sysinfo.SDMISystemInfo = sysinfo + + h.detectiveKVMModuleSupport() + h.detectiveNestSupport() + h.tryEnableNest() + + if err := h.detectiveSyssoftwareInfo(); err != nil { + return err + } + + h.detectiveStorageSystem() + + if options.HostOptions.CheckSystemServices { + if err := h.checkSystemServices(); err != nil { + return err + } + } + return nil +} + +func (h *SHostInfo) checkSystemServices() error { + for _, srv := range []string{"ntpd", "telegraf"} { + srvinst := system_service.GetService(srv) + if srvinst == nil { + return fmt.Errorf("service %s not found", srv) + } else { + if !srvinst.IsInstalled() { + return fmt.Errorf("Service %s not installed", srv) + } + } + } + + for _, srv := range []string{"host_sdnagent"} { + srvinst := system_service.GetService(srv) + if !srvinst.IsInstalled() { + log.Warningf("Service %s not installed", srv) + } else if !srvinst.IsActive() { + srvinst.Start(false) + } + } + return nil +} + +func (h *SHostInfo) detectiveStorageSystem() { + var stype = storagetypes.DISK_TYPE_ROTATE + if options.HostOptions.DiskIsSsd { + stype = storagetypes.DISK_TYPE_SSD + } + h.sysinfo.StorageType = stype +} + +func (h *SHostInfo) fixPathEnv() error { + var paths = []string{ + "/usr/local/sbin", + "/usr/local/bin", + "/sbin", + "/bin", + "/usr/sbin", + "/usr/bin", + } + return os.Setenv("PATH", strings.Join(paths, ":")) +} + +func (h *SHostInfo) DisableHugepages() { + kv := map[string]string{ + "/proc/sys/vm/nr_hugepages": "0", + "/sys/kernel/mm/transparent_hugepage/enabled": "never", + "/sys/kernel/mm/transparent_hugepage/defrag": "never", + } + for k, v := range kv { + h.setSysConfig(k, v) + } +} + +func (h *SHostInfo) EnableTransparentHugepages() { + kv := map[string]string{ + "/proc/sys/vm/nr_hugepages": "0", + "/sys/kernel/mm/transparent_hugepage/enabled": "always", + "/sys/kernel/mm/transparent_hugepage/defrag": "always", + } + for k, v := range kv { + h.setSysConfig(k, v) + } +} + +func (h *SHostInfo) GetMemory() int { + return h.Mem.Total // - options.reserved_memory +} + +func (h *SHostInfo) EnableNativeHugepages() error { + content, err := ioutil.ReadFile("/proc/sys/vm/nr_hugepages") + if err != nil { + return err + } + if string(content) == "0\n" { + kv := map[string]string{ + "/sys/kernel/mm/transparent_hugepage/enabled": "never", + "/sys/kernel/mm/transparent_hugepage/defrag": "never", + } + for k, v := range kv { + h.setSysConfig(k, v) + } + preAllocPagesNum := h.GetMemory()/h.Mem.GetHugepagesizeMb() + 1 + err := timeutils2.CommandWithTimeout(1, "sh", "-c", fmt.Sprintf("echo %d > /proc/sys/vm/nr_hugepages", preAllocPagesNum)).Run() + if err != nil { + log.Errorln(err) + _, err = procutils.NewCommand("sh", "-c", "echo 0 > /proc/sys/vm/nr_hugepages").Run() + if err != nil { + log.Warningf(err.Error()) + } + return fmt.Errorf("Failed to set native hugepages, " + + "the system might have run out of contiguous memory, fall back to 0") + } + } + return nil +} + +func (h *SHostInfo) setSysConfig(cpath, val string) bool { + if fileutils2.Exists(cpath) { + oval, err := ioutil.ReadFile(cpath) + if err != nil { + log.Errorln(err) + return false + } + if string(oval) != val { + err = fileutils2.FilePutContents(cpath, val, false) + if err == nil { + return true + } + log.Errorln(err) + } + } + return false +} + +func (h *SHostInfo) EnableKsm(sleepSec int) { + h.setSysConfig("/sys/kernel/mm/ksm/run", "1") + h.setSysConfig("/sys/kernel/mm/ksm/sleep_millisecs", + fmt.Sprintf("%d", sleepSec*1000)) +} + +func (h *SHostInfo) DisableKsm() { + h.setSysConfig("/sys/kernel/mm/ksm/run", "0") +} + +func (h *SHostInfo) PreventArpFlux() { + h.setSysConfig("/proc/sys/net/ipv4/conf/all/arp_filter", "1") +} + +// Any system wide optimizations +// set swappiness=0 to avoid swap +func (h *SHostInfo) TuneSystem() { + kv := map[string]string{"/proc/sys/vm/swappiness": "0", + "/sys/module/kvm/parameters/ignore_msrs": "1", + } + for k, v := range kv { + h.setSysConfig(k, v) + } +} + +func (h *SHostInfo) resetIptables() error { + for _, tbl := range []string{"filter", "nat", "mangle"} { + _, err := procutils.NewCommand("iptables", "-t", tbl, "-F").Run() + if err != nil { + return fmt.Errorf("Fail to clean NAT iptable: %s", err) + } + } + return nil +} + +func (h *SHostInfo) detectiveKVMModuleSupport() { + if len(h.kvmModuleSupport) == 0 { + h.kvmModuleSupport = h._detectiveKVMModuleSupport() + } +} + +func (h *SHostInfo) _detectiveKVMModuleSupport() string { + var km = KVM_MODULE_UNSUPPORT + if h.modprobeKvmModule(KVM_MODULE_INTEL, false, false) { + km = KVM_MODULE_INTEL + } else if h.modprobeKvmModule(KVM_MODULE_AMD, false, false) { + km = KVM_MODULE_AMD + } + return km +} + +func (h *SHostInfo) modprobeKvmModule(name string, remove, nest bool) bool { + var params = []string{"modprobe"} + if remove { + params = append(params, "-r") + } + params = append(params, name) + if nest { + params = append(params, "nested=1") + } + if _, err := procutils.NewCommand(params[0], params[1:]...).Run(); err != nil { + return false + } + return true +} + +func (h *SHostInfo) getKvmModuleSupport() string { + if len(h.kvmModuleSupport) == 0 { + h.detectiveKVMModuleSupport() + } + return h.kvmModuleSupport +} + +func (h *SHostInfo) detectiveNestSupport() { + if len(h.nestStatus) == 0 { + h.nestStatus = h._detectiveNestSupport() + } + h.sysinfo.Nest = h.nestStatus2Str(h.nestStatus) +} + +func (h *SHostInfo) _detectiveNestSupport() string { + var ( + moduleName = h.getKvmModuleSupport() + nestStatus = HOST_NEST_UNSUPPORT + ) + + if moduleName != KVM_MODULE_UNSUPPORT && h._isNestSupport(moduleName) { + nestStatus = HOST_NEST_SUPPORT + } + return nestStatus +} + +func (h *SHostInfo) _isNestSupport(name string) bool { + output, err := procutils.NewCommand("modinfo", name).Run() + if err != nil { + log.Errorln(err) + return false + } + + // TODO Test + var re = regexp.MustCompile(`parm:\s*nested:`) + for _, line := range strings.Split(string(output), "\n") { + if re.MatchString(line) { + return true + } + } + return false +} + +func (h *SHostInfo) nestStatus2Str(status string) string { + if status == HOST_NEST_ENABLE { + return "enabled" + } else { + return "disbaled" + } +} + +func (h *SHostInfo) tryEnableNest() { + if h.nestStatus == HOST_NEST_SUPPORT { + if h.loadKvmModuleWithNest(h.kvmModuleSupport) { + h.nestStatus = HOST_NEST_ENABLE + } + } + h.sysinfo.Nest = h.nestStatus2Str(h.nestStatus) +} + +func (h *SHostInfo) loadKvmModuleWithNest(name string) bool { + var notload = true + if h.checkKvmModuleInstall(name) { + nest := h.getModuleParameter(name, "nested") + if nest == "Y" { + return true + } + notload = h.unloadKvmModule(name) + } + if notload { + if h.modprobeKvmModule(name, false, true) { + return true + } + } + return false +} + +func (h *SHostInfo) unloadKvmModule(name string) bool { + return h.modprobeKvmModule(name, true, false) +} + +func (h *SHostInfo) getModuleParameter(name, moduel string) string { + pa := path.Join("/sys/module/", strings.Replace(name, "-", "_", -1), "/parameters/", moduel) + if f, err := os.Stat(pa); err == nil { + if f.IsDir() { + return "" + } + cont, err := fileutils2.FileGetContents(pa) + if err != nil { + log.Errorln(err) + return "" + } + return strings.TrimSpace(cont) + } + return "" +} + +func (h *SHostInfo) checkKvmModuleInstall(name string) bool { + output, err := procutils.NewCommand("lsmod").Run() + if err != nil { + log.Errorln(err) + return false + } + for _, line := range strings.Split(string(output), "\n") { + lm := strings.Split(line, " ") + if len(lm) > 0 && utils.IsInStringArray(strings.Replace(name, "-", "_", -1), lm) { + return true + } + } + return false +} + +func (h *SHostInfo) detectiveOsDist() { + files, err := procutils.NewCommand("sh", "-c", "ls /etc/*elease").Run() + if err != nil { + log.Errorln(err) + return + } + re := regexp.MustCompile(`(.+) release ([\d.]+)[^(]*(?:\((.+)\))?`) + for _, file := range strings.Split(string(files), "\n") { + content, err := fileutils2.FileGetContents(file) + if err != nil { + continue + } + m := re.FindStringSubmatch(content) + if len(m) == 4 { + h.sysinfo.OsDistribution = m[1] + h.sysinfo.OsVersion = m[2] + break + } + } + log.Infof("DetectiveOsDist %s %s", h.sysinfo.OsDistribution, h.sysinfo.OsVersion) + if len(h.sysinfo.OsDistribution) == 0 { + log.Errorln("Failed to detect distribution info") + } +} + +func (h *SHostInfo) detectiveKernelVersion() { + out, err := procutils.NewCommand("uname", "-r").Run() + if err != nil { + log.Errorln(err) + } + h.sysinfo.KernelVersion = string(out) +} + +func (h *SHostInfo) detectiveSyssoftwareInfo() error { + h.detectiveOsDist() + h.detectiveKernelVersion() + if err := h.detectiveQemuVersion(); err != nil { + return err + } + h.detectiveOvsVersion() + return nil +} + +func (h *SHostInfo) detectiveQemuVersion() error { + cmd := qemutils.GetQemu(options.HostOptions.DefaultQemuVersion) + version, err := procutils.NewCommand(cmd, "--version").Run() + if err != nil { + log.Errorln(err) + return err + } else { + versions := strings.Split(string(version), "\n") + parts := strings.Split(versions[0], " ") + v := parts[len(parts)-1] + if len(v) > 0 { + log.Infof("Detect qemu version is %s", v) + h.sysinfo.QemuVersion = v + } else { + return fmt.Errorf("Failed to detect qemu version") + } + } + return nil +} + +func (h *SHostInfo) detectiveOvsVersion() { + version, err := procutils.NewCommand("ovs-vsctl", "--version").Run() + if err != nil { + log.Errorln(err) + } else { + versions := strings.Split(string(version), "\n") + parts := strings.Split(versions[0], " ") + v := parts[len(parts)-1] + if len(v) > 0 { + log.Infof("Detect OVS version is %s", v) + h.sysinfo.OvsVersion = v + } else { + log.Errorln("Failed to detect ovs version") + } + } +} + +func (h *SHostInfo) GetMasterIp() string { + if h.MasterNic != nil { + return h.MasterNic.Addr + } + for _, n := range h.Nics { + if len(n.Ip) > 0 { + return n.Ip + } + } + return "" +} + +func (h *SHostInfo) GetMasterMac() string { + if h.MasterNic != nil { + return h.MasterNic.Mac + } + for _, n := range h.Nics { + if len(n.Ip) > 0 { + return n.BridgeDev.GetMac() + } + } + return "" +} + +func (h *SHostInfo) GetMatchNic(bridge, iface, mac string) *SNIC { + for _, nic := range h.Nics { + if nic.BridgeDev.GetMac() == mac || + (nic.Bridge == bridge && nic.Inter == iface) { + return nic + } + } + return nil +} + +func (h *SHostInfo) StartRegister(delay int, callback func()) { + if callback != nil { + h.registerCallback = callback + } + + timeutils2.AddTimeout(time.Duration(delay)*time.Second, h.register) +} + +func (h *SHostInfo) register() { + if !h.isRegistered { + h.fetchAccessNetworkInfo() + } +} + +func (h *SHostInfo) onFail() { + h.StartRegister(30, nil) + panic("register failed, try 30 seconds later...") +} + +func (h *SHostInfo) fetchAccessNetworkInfo() { + masterIp := h.GetMasterIp() + if len(masterIp) == 0 { + panic("master ip not found") + } + params := jsonutils.NewDict() + params.Set("ip", jsonutils.NewString(masterIp)) + params.Set("is_private", jsonutils.JSONTrue) + params.Set("limit", jsonutils.NewInt(0)) + wire, err := hostutils.GetWireOfIp(context.Background(), params) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.ZoneId, err = wire.GetString("zone_id") + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.getZoneInfo(h.ZoneId, false) + } + } +} + +func (h *SHostInfo) GetSession() *mcclient.ClientSession { + return hostutils.GetComputeSession(context.Background()) +} + +func (h *SHostInfo) getZoneInfo(zoneId string, standalone bool) { + var params = jsonutils.NewDict() + params.Set("standalone", jsonutils.NewBool(standalone)) + res, err := modules.Zones.Get(h.GetSession(), + zoneId, params) + if err != nil { + log.Errorln(err) + h.onFail() + } + + h.Zone, _ = res.GetString("name") + h.ZoneId, _ = res.GetString("id") + h.Cloudregion, _ = res.GetString("cloudregion") + h.CloudregionId, _ = res.GetString("cloudregion_id") + if res.Contains("manager_uri") { + h.ZoneManagerUri, _ = res.GetString("manager_uri") + } + if !standalone { + h.getHostInfo(h.ZoneId) + } +} + +func (h *SHostInfo) getHostInfo(zoneId string) { + masterMac := h.GetMasterMac() + if len(masterMac) == 0 { + panic("master mac not found") + } + params := jsonutils.NewDict() + params.Set("any_mac", jsonutils.NewString(masterMac)) + res, err := modules.Hosts.List(h.GetSession(), params) + if err != nil { + log.Errorln(err) + h.onFail() + } + if len(res.Data) == 0 { + h.updateHostRecord("") + } else { + host := res.Data[0] + name, _ := host.GetString("name") + id, _ := host.GetString("id") + h.setHostname(name) + h.updateHostRecord(id) + } +} + +func (h *SHostInfo) setHostname(name string) { + h.FullName = name + _, err := procutils.NewCommand("hostnamectl", "set-hostname", name).Run() + if err != nil { + log.Errorln("Fail to set system hostname: %s", err) + } +} + +func (h *SHostInfo) fetchHostname() string { + if len(options.HostOptions.Hostname) > 0 { + return options.HostOptions.Hostname + } else { + masterIp := h.GetMasterIp() + return "host-" + masterIp + } +} + +func (h *SHostInfo) getSysInfo() *SSysInfo { + return h.sysinfo +} + +func (h *SHostInfo) updateHostRecord(hostId string) { + var method, url string + if len(hostId) == 0 { + method = "POST" + url = fmt.Sprintf("/zones/%s/hosts", h.ZoneId) + } else { + method = "PUT" + url = fmt.Sprintf("/hosts/%s", hostId) + } + content := jsonutils.NewDict() + masterIp := h.GetMasterIp() + if len(masterIp) == 0 { + panic("master ip is none") + } + + if len(hostId) == 0 { + content.Set("name", jsonutils.NewString(h.fetchHostname())) + } + content.Set("access_ip", jsonutils.NewString(masterIp)) + content.Set("access_mac", jsonutils.NewString(h.GetMasterMac())) + var schema = "http" + if options.HostOptions.EnableSsl { + schema = "https" + } + content.Set("manager_uri", jsonutils.NewString(fmt.Sprintf("%s://%s:%d", + schema, masterIp, options.HostOptions.Port))) + content.Set("cpu_count", jsonutils.NewInt(int64(h.Cpu.cpuInfoProc.Count))) + content.Set("node_count", jsonutils.NewInt(int64(h.Cpu.cpuInfoDmi.Nodes))) + content.Set("cpu_desc", jsonutils.NewString(h.Cpu.cpuInfoProc.Model)) + content.Set("cpu_mhz", jsonutils.NewInt(int64(h.Cpu.cpuInfoProc.Freq))) + content.Set("cpu_cache", jsonutils.NewInt(int64(h.Cpu.cpuInfoProc.Cache))) + content.Set("mem_size", jsonutils.NewInt(int64(h.Mem.MemInfo.Total))) + content.Set("storage_driver", jsonutils.NewString(bare1.DISK_DRIVER_LINUX)) + content.Set("storage_type", jsonutils.NewString(h.sysinfo.StorageType)) + content.Set("storage_size", jsonutils.NewInt(int64(storageman.GetManager().GetTotalCapacity()))) + + // TODO optimize content data struct + content.Set("sys_info", jsonutils.Marshal(h.sysinfo)) + content.Set("sn", jsonutils.NewString(h.sysinfo.SN)) + content.Set("host_type", jsonutils.NewString(options.HostOptions.HostType)) + if len(options.HostOptions.Rack) > 0 { + content.Set("rack", jsonutils.NewString(options.HostOptions.Rack)) + } + if len(options.HostOptions.Slots) > 0 { + content.Set("slots", jsonutils.NewString(options.HostOptions.Slots)) + } + content.Set("__meta__", jsonutils.Marshal(h.getSysInfo())) + // content.Set("version", GetVersion()) + body := jsonutils.NewDict() + body.Set("host", content) + session := h.GetSession() + _, res, err := session.JSONVersionRequest("compute", + session.GetEndpointType(), httputils.THttpMethod(method), url, nil, body, "v2") + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.onUpdateHostInfoSucc(res) + } +} + +func (h *SHostInfo) onUpdateHostInfoSucc(body jsonutils.JSONObject) { + hostbody, _ := body.Get("host") + h.HostId, _ = hostbody.GetString("id") + hostname, _ := hostbody.GetString("name") + h.setHostname(hostname) + if memReserved, _ := hostbody.Int("mem_reserved"); memReserved == 0 { + h.updateHostReservedMem() + } else { + h.PutHostOffline() + } +} + +func (h *SHostInfo) updateHostReservedMem() { + content := jsonutils.NewDict() + content.Set("mem_reserved", jsonutils.NewInt(h.getReservedMem())) + res, err := modules.Hosts.Update(h.GetSession(), + h.HostId, content) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.onUpdateHostInfoSucc(res) + } +} + +func (h *SHostInfo) getReservedMem() int64 { + reserved := h.Mem.MemInfo.Total / 10 + if reserved > 4096 { + return 4096 + } + return int64(reserved) +} + +func (h *SHostInfo) PutHostOffline() { + _, err := modules.Hosts.PerformAction( + h.GetSession(), h.HostId, "offline", nil) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.getNetworkInfo() + } +} + +func (h *SHostInfo) PutHostOnline() error { + _, err := modules.Hosts.PerformAction( + h.GetSession(), + h.HostId, "online", nil) + return err +} + +func (h *SHostInfo) getNetworkInfo() { + params := jsonutils.NewDict() + params.Set("details", jsonutils.JSONTrue) + params.Set("limit", jsonutils.NewInt(0)) + res, err := modules.Hostwires.ListDescendent( + h.GetSession(), + h.HostId, params) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + for _, hostwire := range res.Data { + bridge, _ := hostwire.GetString("bridge") + iface, _ := hostwire.GetString("interface") + macAddr, _ := hostwire.GetString("mac_addr") + nic := h.GetMatchNic(bridge, iface, macAddr) + if nic != nil { + wire, _ := hostwire.GetString("wire") + wireId, _ := hostwire.GetString("wire_id") + bandwidth, err := hostwire.Int("bandwidth") + if err != nil { + bandwidth = 1000 + } + nic.SetWireId(wire, wireId, bandwidth) + } else { + log.Warningf("NIC not present %s", hostwire.String()) + } + } + h.uploadNetworkInfo() + } +} + +func (h *SHostInfo) uploadNetworkInfo() { + for _, nic := range h.Nics { + if len(nic.WireId) == 0 { + if len(nic.Network) == 0 { + kwargs := jsonutils.NewDict() + kwargs.Set("ip", jsonutils.NewString(nic.Ip)) + kwargs.Set("is_private", jsonutils.JSONTrue) + kwargs.Set("limit", jsonutils.NewInt(0)) + + wireInfo, err := hostutils.GetWireOfIp(context.Background(), kwargs) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + nic.Network, _ = wireInfo.GetString("name") + h.doUploadNicInfo(nic) + } + + } else { + h.doUploadNicInfo(nic) + } + } else { + h.doSyncNicInfo(nic) + } + } + h.getStoragecacheInfo() +} + +func (h *SHostInfo) doUploadNicInfo(nic *SNIC) { + log.Infof("Upload NIC br:%s if:%s", nic.Bridge, nic.Inter) + content := jsonutils.NewDict() + content.Set("mac", jsonutils.NewString(nic.BridgeDev.GetMac())) + content.Set("wire", jsonutils.NewString(nic.Network)) + content.Set("bridge", jsonutils.NewString(nic.Bridge)) + content.Set("interface", jsonutils.NewString(nic.Inter)) + content.Set("link_up", jsonutils.JSONTrue) + if len(nic.Ip) > 0 { + content.Set("ip_addr", jsonutils.NewString(nic.Ip)) + if nic.Ip == h.GetMasterIp() { + content.Set("nic_type", jsonutils.NewString(bare2.NIC_TYPE_ADMIN)) + } + } + _, err := modules.Hosts.PerformAction(h.GetSession(), + h.HostId, "add-netif", content) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.onUploadNicInfoSucc(nic) + } +} + +func (h *SHostInfo) doSyncNicInfo(nic *SNIC) { + content := jsonutils.NewDict() + content.Set("bridge", jsonutils.NewString(nic.Bridge)) + content.Set("interface", jsonutils.NewString(nic.Inter)) + _, err := modules.Hostwires.Update(h.GetSession(), + h.HostId, nic.Network, nil, content) + if err != nil { + log.Errorln(err) + h.onFail() + } +} + +func (h *SHostInfo) onUploadNicInfoSucc(nic *SNIC) { + res, err := modules.Hostwires.Get(h.GetSession(), h.HostId, nic.Network, nil) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + bridge, _ := res.GetString("bridge") + iface, _ := res.GetString("interface") + macAddr, _ := res.GetString("mac_addr") + nic = h.GetMatchNic(bridge, iface, macAddr) + if nic != nil { + wire, _ := res.GetString("wire") + wireId, _ := res.GetString("wire_id") + bandwidth, err := res.Int("bandwidth") + if err != nil { + bandwidth = 1000 + } + nic.SetWireId(wire, wireId, bandwidth) + } else { + log.Errorln("GetMatchNic failed!!!") + h.onFail() + } + } +} + +func (h *SHostInfo) getStoragecacheInfo() { + path := storageman.GetManager().LocalStorageImagecacheManager.GetPath() + params := jsonutils.NewDict() + params.Set("external_id", jsonutils.NewString(h.HostId)) + params.Set("path", jsonutils.NewString(path)) + res, err := modules.Storagecaches.List( + h.GetSession(), params) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + if len(res.Data) == 0 { + body := jsonutils.NewDict() + body.Set("name", + jsonutils.NewString(fmt.Sprintf( + "local-%s-%s", h.FullName, time.Now().String()))) + body.Set("path", jsonutils.NewString(path)) + body.Set("external_id", jsonutils.NewString(h.HostId)) + sc, err := modules.Storagecaches.Create(h.GetSession(), body) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + scid, _ := sc.GetString("id") + storageman.GetManager(). + LocalStorageImagecacheManager.SetStoragecacheId(scid) + h.getStorageInfo() + } + } else { + scid, _ := res.Data[0].GetString("id") + storageman.GetManager(). + LocalStorageImagecacheManager.SetStoragecacheId(scid) + h.getStorageInfo() + } + } +} + +func (h *SHostInfo) getStorageInfo() { + params := jsonutils.NewDict() + params.Set("details", jsonutils.JSONTrue) + params.Set("limit", jsonutils.NewInt(0)) + res, err := modules.Hoststorages.ListDescendent( + h.GetSession(), + h.HostId, params) + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.onGetStorageInfoSucc(res.Data) + } +} + +func (h *SHostInfo) onGetStorageInfoSucc(hoststorages []jsonutils.JSONObject) { + var detachStorages = []jsonutils.JSONObject{} + storageManager := storageman.GetManager() + + for _, hs := range hoststorages { + storagetype, _ := hs.GetString("storage_type") + mountPoint, _ := hs.GetString("mount_point") + storagecacheId, _ := hs.GetString("storagecache_id") + imagecachePath, _ := hs.GetString("imagecache_path") + storageId, _ := hs.GetString("storage_id") + storageName, _ := hs.GetString("storage") + storageConf, _ := hs.Get("storage_conf") + + log.Infof("Storage %s(%s) mountpoint %s", storageName, storagetype, mountPoint) + + if !utils.IsInStringArray(storagetype, storagetypes.Local) { + storage := storageManager.NewSharedStorageInstance(mountPoint, storagetype) + if storage != nil { + storageManager.Storages = append(storageManager.Storages, storage) + storageManager.InitSharedStorageImageCache( + storagetype, storagecacheId, imagecachePath, storage) + storage.SetStorageInfo(storageId, storageName, storageConf) + } + } else { + // Storage type local + storage := storageManager.GetStorageByPath(mountPoint) + if storage != nil { + storage.SetStorageInfo(storageId, storageName, storageConf) + } else { + // XXX hack: storage type baremetal is a converted host,reserve storage + if storagetype != storagetypes.STORAGE_BAREMETAL { + detachStorages = append(detachStorages, hs) + } + } + } + } + + if len(detachStorages) > 0 { + go StartDetachStorages(detachStorages) + } + + h.uploadStorageInfo() +} + +func (h *SHostInfo) uploadStorageInfo() { + for _, s := range storageman.GetManager().Storages { + res, err := s.SyncStorageInfo() + if err != nil { + log.Errorln(err) + h.onFail() + } else { + h.onSyncStorageInfoSucc(s, res) + } + } + h.getIsolatedDevices() +} + +func (h *SHostInfo) onSyncStorageInfoSucc(storage storageman.IStorage, storageInfo jsonutils.JSONObject) { + if len(storage.GetId()) == 0 { + id, _ := storageInfo.GetString("id") + name, _ := storageInfo.GetString("name") + storageConf, _ := storageInfo.Get("storage_conf") + storage.SetStorageInfo(id, name, storageConf) + h.attachStorage(storage) + } +} + +func (h *SHostInfo) attachStorage(storage storageman.IStorage) { + content := jsonutils.NewDict() + content.Set("mount_point", jsonutils.NewString(storage.GetPath())) + _, err := modules.Hoststorages.Attach(h.GetSession(), + h.HostId, storage.GetId(), content) + if err != nil { + log.Errorln(err) + h.onFail() + } +} + +func (h *SHostInfo) getIsolatedDevices() { + params := jsonutils.NewDict() + params.Set("details", jsonutils.JSONTrue) + params.Set("limit", jsonutils.NewInt(0)) + params.Set("host", jsonutils.NewString(h.GetHostId())) + res, err := modules.IsolatedDevices.List(h.GetSession(), params) + if err != nil { + log.Errorf("getIsolatedDevices: %v", err) + h.onFail() + return + } + h.onGetIsolatedDeviceSucc(res.Data) +} + +func (h *SHostInfo) onGetIsolatedDeviceSucc(objs []jsonutils.JSONObject) { + for _, obj := range objs { + info := isolated_device.CloudDeviceInfo{} + obj.Unmarshal(&info) + dev := h.IsolatedDeviceMan.GetDeviceByIdent(info.VendorDeviceId, info.Addr) + if dev != nil { + dev.SetDeviceInfo(info) + } else { + // detach device + h.IsolatedDeviceMan.AppendDetachedDevice(&info) + } + } + h.IsolatedDeviceMan.StartDetachTask() + if err := h.IsolatedDeviceMan.BatchCustomProbe(); err != nil { + log.Errorf("Device probe error: %v", err) + h.onFail() + return + } + h.uploadIsolatedDevices() +} + +func (h *SHostInfo) uploadIsolatedDevices() { + for _, dev := range h.IsolatedDeviceMan.Devices { + if err := dev.SyncDeviceInfo(h); err != nil { + log.Errorf("Sync device %s: %v", dev.String(), err) + h.onFail() + return + } + } + h.onSucc() +} + +func (h *SHostInfo) onSucc() { + if !h.stopped && !h.isRegistered { + log.Infof("Host registration process success....") + if err := h.save(); err != nil { + panic(err.Error()) + } + h.StartPinger() + if h.registerCallback != nil { + h.registerCallback() + } + h.isRegistered = true + + // Notify caller, host register is success + close(h.IsRegistered) + } +} + +func (h *SHostInfo) StartPinger() { + h.pinger = NewHostPingTask(options.HostOptions.PingRegionInterval) + if h.pinger != nil { + go h.pinger.Start() + } +} + +func (h *SHostInfo) save() error { + if h.saved { + return nil + } else { + h.saved = true + } + + if err := h.registerHostlocalServer(); err != nil { + return err + } + // TODO XXX >>> ??? + // file put content + if err := h.setupBridges(); err != nil { + return err + } + return nil +} + +func (h *SHostInfo) setupBridges() error { + for _, n := range h.Nics { + if err := n.BridgeDev.WarmupConfig(); err != nil { + log.Errorln(err) + return err + } + } + return nil +} + +func (h *SHostInfo) registerHostlocalServer() error { + for _, n := range h.Nics { + mac := h.GetMasterMac() + if len(mac) == 0 { + panic("len mac == 0") + } + ip := h.GetMasterIp() + if len(ip) == 0 { + panic("len ip == 0") + } + + err := n.BridgeDev.RegisterHostlocalServer(mac, ip) + if err != nil { + return err + } + } + return nil +} + +func (h *SHostInfo) stop() { + log.Infof("Host Info stop ...") + h.unregister() + if h.pinger != nil { + h.pinger.Stop() + } + for _, nic := range h.Nics { + nic.ExitCleanup() + } +} + +func (h *SHostInfo) unregister() { + h.stopped = true + _, err := modules.Hosts.PerformAction( + h.GetSession(), h.HostId, "offline", nil) + if err != nil { + log.Errorln(err) + } +} + +func (h *SHostInfo) OnCatalogChanged(catalog mcclient.KeystoneServiceCatalogV3) { + if options.HostOptions.ManageNtpConfiguration { + ntpd := system_service.GetService("ntpd") + urls, _ := catalog.GetServiceURLs("ntp", options.HostOptions.Region, "", "internalURL") + if len(urls) > 0 { + log.Infof("Get Ntp urls: %v", urls) + } else { + urls = []string{"ntp://cn.pool.ntp.org", + "ntp://0.cn.pool.ntp.org", + "ntp://1.cn.pool.ntp.org", + "ntp://2.cn.pool.ntp.org", + "ntp://3.cn.pool.ntp.org"} + } + if !reflect.DeepEqual(ntpd.GetConf(), urls) && !ntpd.IsActive() { + ntpd.SetConf(urls) + ntpd.BgReload(map[string]interface{}{"servers": urls}) + } + } + telegraf := system_service.GetService("telegraf") + conf := map[string]interface{}{} + conf["hostname"] = h.getHostname() + conf["tags"] = map[string]string{ + "host_id": h.HostId, + "zone_id": h.ZoneId, + "zone": h.Zone, + "cloudregion_id": h.CloudregionId, + "cloudregion": h.Cloudregion, + "region": options.HostOptions.Region, + "host_ip": h.GetMasterIp(), + "platform": "kvm", + "res_type": "host", + } + conf["nics"] = h.getNicsTelegrafConf() + urls, _ := catalog.GetServiceURLs("kafka", options.HostOptions.Region, "", "internalURL") + if len(urls) > 0 { + conf["influxdb"] = map[string]interface{}{"url": urls, "database": "telegraf"} + } + if !reflect.DeepEqual(telegraf.GetConf(), conf) || !telegraf.IsActive() { + telegraf.SetConf(conf) + telegraf.BgReload(conf) + } + + urls, _ = catalog.GetServiceURLs("elasticsearch", + options.HostOptions.Region, "zone", "internalURL") + if len(urls) > 0 { + conf["elasticsearch"] = map[string]interface{}{"url": urls[0]} + fluentbit := system_service.GetService("fluentbit") + if !reflect.DeepEqual(fluentbit.GetConf(), conf) || !fluentbit.IsActive() { + fluentbit.SetConf(conf) + fluentbit.BgReload(conf) + } + } +} + +func (h *SHostInfo) getNicsTelegrafConf() []map[string]interface{} { + var ret = make([]map[string]interface{}, 0) + for i, n := range h.Nics { + ret = append(ret, map[string]interface{}{ + "name": n.Inter, + "alias": fmt.Sprintf("eth%d", i), + "speed": n.Bandwidth, + }) + ret = append(ret, map[string]interface{}{ + "name": n.Inter, + "alias": fmt.Sprintf("br%d", i), + "speed": n.Bandwidth, + }) + } + return ret +} + +func (h *SHostInfo) getHostname() string { + if len(h.FullName) > 0 { + return h.FullName + } + return h.fetchHostname() +} + +func NewHostInfo() (*SHostInfo, error) { + var res = new(SHostInfo) + res.sysinfo = &SSysInfo{} + cpu, err := DetectCpuInfo() + if err != nil { + return nil, err + } else { + res.Cpu = cpu + } + + mem, err := DetectMemoryInfo() + if err != nil { + return nil, err + } else { + res.Mem = mem + } + + res.Nics = make([]*SNIC, 0) + res.IsRegistered = make(chan struct{}) + return res, nil +} + +var hostInfo *SHostInfo + +func Instance() *SHostInfo { + if hostInfo == nil { + var err error + hostInfo, err = NewHostInfo() + if err != nil { + log.Fatalf(err.Error()) + } + } + return hostInfo +} + +func Stop() { + hostInfo.stop() +} diff --git a/pkg/hostman/hostinfo/hostinfo_test.go b/pkg/hostman/hostinfo/hostinfo_test.go new file mode 100644 index 0000000000..f82a679058 --- /dev/null +++ b/pkg/hostman/hostinfo/hostinfo_test.go @@ -0,0 +1,42 @@ +package hostinfo + +import ( + "testing" +) + +func TestSHostInfo_Start(t *testing.T) { + type fields struct { + isRegistered bool + kvmModuleSupport string + nestStatus string + Cpu *SCPUInfo + Mem *SMemory + sysinfo *SSysInfo + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "HostInfo Test", + fields{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &SHostInfo{ + isRegistered: tt.fields.isRegistered, + kvmModuleSupport: tt.fields.kvmModuleSupport, + nestStatus: tt.fields.nestStatus, + Cpu: tt.fields.Cpu, + Mem: tt.fields.Mem, + sysinfo: tt.fields.sysinfo, + } + if err := h.Start(); (err != nil) != tt.wantErr { + t.Errorf("SHostInfo.Start() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/hostman/hostinfo/hostinfohelper.go b/pkg/hostman/hostinfo/hostinfohelper.go new file mode 100644 index 0000000000..04b2686518 --- /dev/null +++ b/pkg/hostman/hostinfo/hostinfohelper.go @@ -0,0 +1,310 @@ +package hostinfo + +import ( + "bufio" + "context" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/mem" + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/util/regutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge" + "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostdhcp" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "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/sysutils" +) + +type SCPUInfo struct { + CpuCount int + cpuFreq int64 // MHZ + cpuFeatures []string + + cpuInfoProc *types.SCPUInfo + cpuInfoDmi *types.SDMICPUInfo +} + +func DetectCpuInfo() (*SCPUInfo, error) { + cpuinfo := new(SCPUInfo) + cpuCount, _ := cpu.Counts(true) + cpuinfo.CpuCount = cpuCount + spec, err := cpuinfo.fetchCpuSpecs() + if err != nil { + return nil, err + } + strCpuFreq := spec["cpu_freq"] + freq, err := strconv.ParseFloat(strCpuFreq, 64) + if err != nil { + log.Errorln(err) + return nil, err + } + cpuinfo.cpuFreq = int64(freq) + log.Infof("cpuinfo freq %d", cpuinfo.cpuFreq) + + cpuinfo.cpuFeatures = strings.Split(spec["flags"], " ") + + // cpu.Percent(interval, false) + ret, err := fileutils2.FileGetContents("/proc/cpuinfo") + if err != nil { + log.Errorln(err) + return nil, err + } + cpuinfo.cpuInfoProc, err = sysutils.ParseCPUInfo(strings.Split(ret, "\n")) + if err != nil { + log.Errorln(err) + return nil, err + } + bret, err := procutils.NewCommand("dmidecode", "-t", "4").Run() + if err != nil { + log.Errorln(err) + return nil, err + } + cpuinfo.cpuInfoDmi = sysutils.ParseDMICPUInfo(strings.Split(string(bret), "\n")) + if err != nil { + log.Errorln(err) + return nil, err + } + return cpuinfo, nil +} + +func (c *SCPUInfo) fetchCpuSpecs() (map[string]string, error) { + f, err := os.Open("/proc/cpuinfo") + if err != nil { + return nil, err + } + defer f.Close() + + var spec = make(map[string]string, 0) + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + colon := strings.Index(line, ":") + if colon > 0 { + key := strings.TrimSpace(line[:colon]) + val := strings.TrimSpace(line[colon+1:]) + if key == "cpu MHz" { + spec["cpu_freq"] = val + } else if key == "flags" { + spec["flags"] = val + } + } + } + if err := scanner.Err(); err != nil { + log.Errorln(err) + return nil, err + } + return spec, nil +} + +// percentInterval(ms) +func (c *SCPUInfo) GetJsonDesc(percentInterval int) { + // perc, err := cpu.Percent(time.Millisecond*percentInterval, false) + // os. ?????可能不需要要写 +} + +type SMemory struct { + Total int + Free int + Used int + MemInfo *types.SDMIMemInfo +} + +func DetectMemoryInfo() (*SMemory, error) { + var smem = new(SMemory) + info, err := mem.VirtualMemory() + if err != nil { + return nil, err + } + smem.Total = int(info.Total / 1024 / 1024) + smem.Free = int(info.Available / 1024 / 1024) + smem.Used = smem.Total - smem.Free + ret, err := procutils.NewCommand("dmidecode", "-t", "17").Run() + if err != nil { + return nil, err + } + smem.MemInfo = sysutils.ParseDMIMemInfo(strings.Split(string(ret), "\n")) + return smem, nil +} + +func (m *SMemory) GetHugepagesizeMb() int { + file, err := os.Open("/proc/meminfo") + if err != nil { + log.Errorln(err) + return 0 + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "Hugepagesize:") { + re := regexp.MustCompile(`\s+`) + segs := re.Split(line, -1) + v, err := strconv.Atoi(segs[1]) + if err != nil { + log.Errorln(err) + return 0 + } + return int(v) / 1024 + } + } + if err := scanner.Err(); err != nil { + log.Errorln(err) + } + return 0 +} + +type SNIC struct { + Inter string + Bridge string + Ip string + Network string + WireId string + + Bandwidth int + BridgeDev hostbridge.IBridgeDriver + dhcpServer *hostdhcp.SGuestDHCPServer +} + +func (n *SNIC) EnableDHCPRelay() bool { + v4Ip, err := netutils.NewIPV4Addr(n.Ip) + if err != nil { + log.Errorln(err) + return false + } + if len(options.HostOptions.GoDhcpRelay) > 0 && !netutils.IsExitAddress(v4Ip) { + return true + } else { + return false + } +} + +func (n *SNIC) SetupDhcpRelay() error { + if n.EnableDHCPRelay() { + if err := n.dhcpServer.RelaySetup(n.Ip); err != nil { + return err + } + } + return nil +} + +func (n *SNIC) SetWireId(wire, wireId string, bandwidth int64) { + n.Network = wire + n.WireId = wireId + n.Bandwidth = int(bandwidth) +} + +func (n *SNIC) ExitCleanup() { + n.BridgeDev.CleanupConfig() + log.Infof("Stop DHCP Server") + // TODO stop dhcp server +} + +func NewNIC(desc string) (*SNIC, error) { + nic := new(SNIC) + data := strings.Split(desc, "/") + nic.Inter = data[0] + nic.Bridge = data[1] + if regutils.MatchIP4Addr(data[2]) { + nic.Ip = data[2] + } else { + nic.Network = data[2] + } + nic.Bandwidth = 1000 + + log.Infof("IP %s/%s/%s", nic.Ip, nic.Bridge, nic.Inter) + // 这是干啥呢 ??? + if len(nic.Ip) > 0 { + var max, wait = 30, 0 + for wait < max { + inf := netutils2.NewNetInterface(nic.Inter) + if inf.Addr == nic.Ip { + break + } + br := netutils2.NewNetInterface(nic.Bridge) + if br.Addr == nic.Ip { + break + } + time.Sleep(time.Second * 2) + wait += 1 + } + } + + var err error + + nic.BridgeDev, err = hostbridge.NewDriver(options.HostOptions.BridgeDriver, + nic.Bridge, nic.Inter, nic.Ip) + if err != nil { + log.Errorln(err) + return nil, err + } + + confirm, err := nic.BridgeDev.ConfirmToConfig(nic.BridgeDev.Exists(), nic.BridgeDev.Interfaces()) + if err != nil { + log.Errorln(err) + return nil, err + } + if !confirm { + log.Infof("Not confirm to configuration") + if err = nic.BridgeDev.Setup(); err != nil { + log.Errorln(err) + return nil, err + } + time.Sleep(time.Second * 1) + } else { + log.Infof("Confirm to configuration!!") + } + + var dhcpRelay []string + if nic.EnableDHCPRelay() { + dhcpRelay = options.HostOptions.GoDhcpRelay + } + nic.dhcpServer, err = hostdhcp.NewGuestDHCPServer(nic.Bridge, dhcpRelay) + if err != nil { + return nil, err + } + nic.dhcpServer.Start() + return nic, nil +} + +type SSysInfo struct { + *types.SDMISystemInfo + + Nest string `json:"nest,omitempty"` + OsDistribution string `json:"os_distribution"` + OsVersion string `json:"os_version"` + KernelVersion string `json:"kernel_version"` + QemuVersion string `json:"qemu_version"` + OvsVersion string `json:"ovs_version"` + + StorageType string `json:"storage_type"` +} + +func StartDetachStorages(hs []jsonutils.JSONObject) { + for len(hs) > 0 { + hostId, _ := hs[0].GetString("host_id") + storageId, _ := hs[0].GetString("storage_id") + _, err := modules.Hoststorages.Detach( + hostutils.GetComputeSession(context.Background()), + hostId, storageId, nil) + if err != nil { + log.Errorf("Host %s detach storage %s failed: %s", + hostId, storageId, err) + time.Sleep(30 * time.Second) + } else { + hs = hs[1:] + } + } +} diff --git a/pkg/hostman/hostinfo/hostpinger.go b/pkg/hostman/hostinfo/hostpinger.go new file mode 100644 index 0000000000..78616274dd --- /dev/null +++ b/pkg/hostman/hostinfo/hostpinger.go @@ -0,0 +1,85 @@ +package hostinfo + +import ( + "context" + "time" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +type SHostPingTask struct { + interval int // second + running bool +} + +type SEndpoint struct { + Id string `json:"id"` + Interface string `json:"interface"` + Region string `json:"region"` + Region_id string `json:"region_id"` + Url string `json:"url"` + Name string `json:"name"` +} + +type SCatalog struct { + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Endpoints []SEndpoint `json:"endpoint"` +} + +func NewCatalog() *SCatalog { + return &SCatalog{ + Endpoints: make([]SEndpoint, 0), + } +} + +func NewHostPingTask(interval int) *SHostPingTask { + if interval <= 0 { + return nil + } + return &SHostPingTask{interval, true} +} + +func (p *SHostPingTask) Start() { + var ( + div = 1 + hostId = Instance().GetHostId() + ) + for { + time.Sleep(time.Duration(p.interval/div) * time.Second) + if !p.running { + return + } + res, err := modules.Hosts.PerformAction(hostutils.GetComputeSession(context.Background()), + hostId, "ping", nil) + if err != nil { + div = 3 + } else { + name, err := res.GetString("name") + if err != nil { + Instance().setHostname(name) + } + catalog, err := res.Get("catalog") + if err != nil { + cl := make(mcclient.KeystoneServiceCatalogV3, 0) + err = catalog.Unmarshal(&cl) + if err != nil { + log.Errorln(err) + continue + } + + Instance().OnCatalogChanged(cl) + } + } + } +} + +func (p *SHostPingTask) Stop() { + if p.running { + p.running = false + } +} diff --git a/pkg/hostman/hostmetrics/doc.go b/pkg/hostman/hostmetrics/doc.go new file mode 100644 index 0000000000..3ef0d0730f --- /dev/null +++ b/pkg/hostman/hostmetrics/doc.go @@ -0,0 +1 @@ +package hostmetrics // import "yunion.io/x/onecloud/pkg/hostman/hostmetrics" diff --git a/pkg/hostman/hostmetrics/hostmetrics.go b/pkg/hostman/hostmetrics/hostmetrics.go new file mode 100644 index 0000000000..a84e823885 --- /dev/null +++ b/pkg/hostman/hostmetrics/hostmetrics.go @@ -0,0 +1,489 @@ +package hostmetrics + +import ( + "context" + "fmt" + "reflect" + "runtime/debug" + "strings" + "time" + "unicode" + + "github.com/shirou/gopsutil/host" + psnet "github.com/shirou/gopsutil/net" + "github.com/shirou/gopsutil/process" + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/hostman/guestman" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +const ( + TelegrafServer = "http://localhost:8087/write" + MeasurementsPrefix = "vm_" +) + +type SHostMetricsCollector struct { + ReportInterval int // seconds + running bool + LastCollectTime time.Time + waitingReportData []string + guestMonitor *SGuestMonitorCollector +} + +var hostMetricsCollector *SHostMetricsCollector + +func Init() { + if hostMetricsCollector == nil { + hostMetricsCollector = NewHostMetricsCollector() + } +} + +func Start() { + if hostMetricsCollector != nil { + go hostMetricsCollector.Start() + } +} + +func Stop() { + if hostMetricsCollector != nil { + hostMetricsCollector.Stop() + } +} + +func (m *SHostMetricsCollector) Start() { + m.running = true + for m.running { + m.runMain() + time.Sleep(time.Second * 1) + } +} + +func (m *SHostMetricsCollector) Stop() { + m.running = false +} + +func (m *SHostMetricsCollector) runMain() { + timeBegin := time.Now() + elapse := timeBegin.Sub(m.LastCollectTime) + if elapse < time.Second*time.Duration(m.ReportInterval) { + return + } else { + m.LastCollectTime = timeBegin + } + m.runMonitor() +} + +func (m *SHostMetricsCollector) runMonitor() { + reportData := m.collectReportData() + if options.HostOptions.EnableTelegraf && len(reportData) > 0 { + m.reportUsageToTelegraf(reportData) + } +} + +func (m *SHostMetricsCollector) reportUsageToTelegraf(data string) { + body := strings.NewReader(data) + res, err := httputils.Request(httputils.GetDefaultClient(), context.Background(), "POST", TelegrafServer, nil, body, false) + if err != nil { + log.Errorf("Upload guest metric failed: %s", err) + return + } + if res.StatusCode != 204 { + log.Errorf("upload guest metric failed %d", res.StatusCode) + timestamp := time.Now().UnixNano() + for _, line := range strings.Split(data, "\n") { + m.waitingReportData = append(m.waitingReportData, + fmt.Sprintf("%s %d", line, timestamp)) + } + } else { + if len(m.waitingReportData) > 0 { + oldDatas := strings.Join(m.waitingReportData, "\n") + body = strings.NewReader(oldDatas) + res, err = httputils.Request(httputils.GetDefaultClient(), context.Background(), "POST", TelegrafServer, nil, body, false) + if res.StatusCode == 204 { + m.waitingReportData = m.waitingReportData[len(m.waitingReportData):] + } else { + log.Errorf("upload guest metric failed code: %s", res.StatusCode) + } + } + } +} + +func (m *SHostMetricsCollector) collectReportData() string { + if len(m.waitingReportData) > 60 { + m.waitingReportData = m.waitingReportData[1:] + } + return m.guestMonitor.CollectReportData() +} + +func NewHostMetricsCollector() *SHostMetricsCollector { + return &SHostMetricsCollector{ + ReportInterval: options.HostOptions.ReportInterval, + waitingReportData: make([]string, 0), + guestMonitor: NewGuestMonitorCollector(), + } +} + +type SGuestMonitorCollector struct { + monitors map[string]*SGuestMonitor + prevPids map[string]int + prevReportData *jsonutils.JSONDict +} + +func NewGuestMonitorCollector() *SGuestMonitorCollector { + return &SGuestMonitorCollector{ + monitors: make(map[string]*SGuestMonitor, 0), + prevPids: make(map[string]int, 0), + prevReportData: jsonutils.NewDict(), + } +} + +func (s *SGuestMonitorCollector) GetGuests() map[string]*SGuestMonitor { + var err error + gms := make(map[string]*SGuestMonitor, 0) + guetmananger := guestman.GetGuestManager() + for _, guest := range guetmananger.Servers { + pid := guest.GetPid() + if pid > 0 { + guestName, _ := guest.Desc.GetString("name") + guestId := guest.GetId() + nicsDesc, _ := guest.Desc.GetArray("nics") + nics := make([]jsonutils.JSONObject, len(nicsDesc)) + copy(nics, nicsDesc) + vcpuCount, _ := guest.Desc.Int("cpu") + gm, ok := s.monitors[guestId] + if ok && gm.Pid == pid { + delete(s.monitors, guestId) + gm.UpdateNicsDesc(nics) + gm.UpdateCpuCount(int(vcpuCount)) + } else { + delete(s.monitors, guestId) + gm, err = NewGuestMonitor(guestName, guestId, pid, nics, int(vcpuCount)) + if err != nil { + log.Errorln(err) + continue + } + } + gms[guestId] = gm + } + } + s.monitors = gms + return gms +} + +func (s *SGuestMonitorCollector) CollectReportData() (ret string) { + defer func() { + if r := recover(); r != nil { + log.Errorln(r) + debug.PrintStack() + } + }() + gms := s.GetGuests() + s.cleanedPrevData(gms) + reportData := jsonutils.NewDict() + for _, gm := range gms { + prevUsage, _ := s.prevReportData.Get(gm.Id) + usage := s.collectGmReport(gm, prevUsage) + reportData.Set(gm.Id, usage) + s.prevPids[gm.Id] = gm.Pid + } + + s.prevReportData = jsonutils.NewDict() + for k, v := range reportData.Value() { + s.prevReportData.Set(k, v) + } + ret = s.toTelegrafReportData(reportData) + return +} + +func (s *SGuestMonitorCollector) toTelegrafReportData(data *jsonutils.JSONDict) string { + ret := []string{} + for guestId, report := range data.Value() { + var vmName, vmIp string + if gm, ok := s.monitors[guestId]; ok { + vmName = gm.Name + vmIp = gm.Ip + } + for metrics, stat := range report.(*jsonutils.JSONDict).Value() { + tags := map[string]string{ + "vm_id": guestId, "vm_name": vmName, "vm_ip": vmIp, + "is_vm": "true", "platform": "kvm", + } + if val, ok := stat.(*jsonutils.JSONDict); ok { + line := s.addTelegrafLine(metrics, tags, val) + ret = append(ret, line) + } else if val, ok := stat.(*jsonutils.JSONArray); ok { + for _, statItem := range val.Value() { + line := s.addTelegrafLine(metrics, tags, statItem.(*jsonutils.JSONDict)) + ret = append(ret, line) + } + } + } + } + return strings.Join(ret, "\n") +} + +func (s *SGuestMonitorCollector) addTelegrafLine( + metrics string, tags map[string]string, stat *jsonutils.JSONDict, +) string { + meta, _ := stat.GetMap("meta") + stat.Remove("meta") + if meta != nil { + delete(meta, "uptime") + } + + var tagArr = []string{} + for k, v := range tags { + tagArr = append(tagArr, fmt.Sprintf("%s=%s", k, v)) + } + tagStr := strings.Join(tagArr, ",") + + var statArr = []string{} + for k, v := range stat.Value() { + statArr = append(statArr, fmt.Sprintf("%s=%s", k, v.String())) + } + statStr := strings.Join(statArr, ",") + return fmt.Sprintf("%s,%s %s", metrics, tagStr, statStr) +} + +func (s *SGuestMonitorCollector) cleanedPrevData(gms map[string]*SGuestMonitor) { + for guestId := range s.prevReportData.Value() { + if gm, ok := gms[guestId]; !ok { + s.prevReportData.Remove(guestId) + delete(s.prevPids, guestId) + } else { + if s.prevPids[guestId] != gm.Pid { + s.prevReportData.Remove(guestId) + delete(s.prevPids, guestId) + } + } + } +} + +func (s *SGuestMonitorCollector) collectGmReport( + gm *SGuestMonitor, prevUsage jsonutils.JSONObject, +) *jsonutils.JSONDict { + if prevUsage == nil { + prevUsage = jsonutils.NewDict() + } + gmData := jsonutils.NewDict() + v := reflect.ValueOf(gm) + for _, k := range []string{"Netio", "Cpu", "Diskio", "Mem"} { + res := v.MethodByName(k).Call(nil) + if !res[0].IsNil() { + val := res[0].Interface() + in := []rune(k) + in[0] = unicode.ToLower(in[0]) + key := MeasurementsPrefix + string(in) + gmData.Set(key, val.(jsonutils.JSONObject)) + } + } + gmNetio := MeasurementsPrefix + "netio" + netio1, err1 := gmData.Get(gmNetio) + netio2, err2 := prevUsage.Get(gmNetio) + if err1 == nil && err2 == nil { + s.addNetio(netio1, netio2, + []string{"bits_recv", "bits_sent", "packets_sent", "packets_recv"}) + } + + gmDiskio := MeasurementsPrefix + "diskio" + diskio1, err1 := gmData.Get(gmDiskio) + diskio2, err2 := prevUsage.Get(gmDiskio) + if err1 == nil && err2 == nil { + s.addDiskio(diskio1, diskio2, []string{"read_bytes", "write_bytes"}) + } + return gmData +} + +func (s *SGuestMonitorCollector) GetIoFiledName(field string) string { + kmap := map[string]string{ + "bits": "bps", "bytes": "bps", "packets": "pps", + } + for k, v := range kmap { + if strings.Contains(field, k) { + return strings.Replace(field, k, v, -1) + } + } + return field + "_per_seconds" +} + +func (s *SGuestMonitorCollector) reportIo(curInfo, prevInfo jsonutils.JSONObject, fields []string, +) *jsonutils.JSONDict { + ioInfo := jsonutils.NewDict() + + var timeCur int64 + uptime, err := curInfo.Get("meta") + if err == nil { + timeCur, _ = uptime.Int("uptime") + } + + var timeOld int64 + uptime, err = prevInfo.Get("meta") + if err == nil { + timeOld, _ = uptime.Int("uptime") + } + diffTime := timeCur - timeOld + + for _, field := range fields { + info, _ := curInfo.Float(field) + ioInfo.Set(s.GetIoFiledName(field), jsonutils.NewFloat(info/float64(diffTime))) + } + return ioInfo +} + +func (s *SGuestMonitorCollector) addDiskio(curInfo, prevInfo jsonutils.JSONObject, fields []string) { + ioInfo := s.reportIo(curInfo, prevInfo, fields) + curInfo.(*jsonutils.JSONDict).Update(ioInfo) +} + +func (s *SGuestMonitorCollector) addNetio(curInfo, prevInfo jsonutils.JSONObject, fields []string) { + curMap, _ := curInfo.GetMap() + prevMap, _ := prevInfo.GetMap() + for _, v1 := range curMap { + for _, v2 := range prevMap { + if v1.Contains("meta", "ip") && v2.Contains("meta", "ip") { + ip1, _ := v1.GetString("meta", "ip") + ip2, _ := v2.GetString("meta", "ip") + if ip1 == ip2 { + ioInfo := s.reportIo(v1, v2, fields) + v1.(*jsonutils.JSONDict).Update(ioInfo) + } + } + } + } +} + +type SGuestMonitor struct { + Name string + Id string + Pid int + Nics []jsonutils.JSONObject + CpuCnt int + Ip string + Process *process.Process +} + +func NewGuestMonitor(name, id string, pid int, nics []jsonutils.JSONObject, cpuCount int, +) (*SGuestMonitor, error) { + var ip string + if len(nics) >= 1 { + ip, _ = nics[0].GetString("ip") + } + proc, err := process.NewProcess(int32(pid)) + if err != nil { + return nil, err + } + return &SGuestMonitor{name, id, pid, nics, cpuCount, ip, proc}, nil +} + +func (m *SGuestMonitor) UpdateNicsDesc(nics []jsonutils.JSONObject) { + m.Nics = nics +} + +func (m *SGuestMonitor) UpdateCpuCount(vcpuCount int) { + if vcpuCount < 1 { + vcpuCount = 1 + } + m.CpuCnt = vcpuCount +} + +func (m *SGuestMonitor) Netio() jsonutils.JSONObject { + if len(m.Nics) == 0 { + return nil + } + netstats, err := psnet.IOCounters(true) + if err != nil { + return nil + } + + var res = jsonutils.NewArray() + for i, nic := range m.Nics { + ifname, _ := nic.GetString("ifname") + var nicStat *psnet.IOCountersStat + for j, netstat := range netstats { + if netstat.Name == ifname { + nicStat = &netstats[j] + } + } + if nicStat == nil { + continue + } + data := jsonutils.NewDict() + meta := jsonutils.NewDict() + + ip, _ := nic.GetString("ip") + ipv4, _ := netutils.NewIPV4Addr(ip) + if netutils.IsExitAddress(ipv4) { + meta.Set("ip_type", jsonutils.NewString("external")) + } else { + meta.Set("ip_type", jsonutils.NewString("internal")) + } + + netId, _ := nic.GetString("net_id") + meta.Set("ip", jsonutils.NewString(ip)) + meta.Set("index", jsonutils.NewInt(int64(i))) + meta.Set("ifname", jsonutils.NewString(ifname)) + meta.Set("net_id", jsonutils.NewString(netId)) + uptime, _ := host.Uptime() + meta.Set("uptime", jsonutils.NewInt(int64(uptime))) + data.Set("meta", meta) + data.Set("bits_sent", jsonutils.NewInt(int64(nicStat.BytesRecv*8))) + data.Set("bits_recv", jsonutils.NewInt(int64(nicStat.BytesSent*8))) + data.Set("packets_sent", jsonutils.NewInt(int64(nicStat.PacketsRecv))) + data.Set("packets_recv", jsonutils.NewInt(int64(nicStat.PacketsSent))) + data.Set("err_in", jsonutils.NewInt(int64(nicStat.Errout))) + data.Set("err_out", jsonutils.NewInt(int64(nicStat.Errin))) + data.Set("drop_in", jsonutils.NewInt(int64(nicStat.Dropout))) + data.Set("drop_out", jsonutils.NewInt(int64(nicStat.Dropin))) + res.Add(data) + } + return res +} + +func (m *SGuestMonitor) Cpu() jsonutils.JSONObject { + percent, _ := m.Process.CPUPercent() + cpuTimes, _ := m.Process.Times() + ret := jsonutils.NewDict() + ret.Set("usage_active", jsonutils.NewFloat(percent)) + ret.Set("cpu_usage_idle_pcore", jsonutils.NewFloat(100-percent/float64(m.CpuCnt))) + ret.Set("cpu_usage_pcore", jsonutils.NewFloat(percent/float64(m.CpuCnt))) + ret.Set("cpu_time_user", jsonutils.NewFloat(cpuTimes.User)) + ret.Set("cpu_time_system", jsonutils.NewFloat(cpuTimes.System)) + ret.Set("cpu_count", jsonutils.NewInt(int64(m.CpuCnt))) + + threadCnt, _ := m.Process.NumThreads() + ret.Set("thread_count", jsonutils.NewInt(int64(threadCnt))) + return ret +} + +func (m *SGuestMonitor) Diskio() jsonutils.JSONObject { + io, err := m.Process.IOCounters() + if err != nil { + log.Errorln(err) + return nil + } + ret := jsonutils.NewDict() + meta := jsonutils.NewDict() + + uptime, _ := host.Uptime() + meta.Set("uptime", jsonutils.NewInt(int64(uptime))) + ret.Set("meta", meta) + ret.Set("read_bytes", jsonutils.NewInt(int64(io.ReadBytes))) + ret.Set("write_bytes", jsonutils.NewInt(int64(io.WriteBytes))) + return ret +} + +func (m *SGuestMonitor) Mem() jsonutils.JSONObject { + mem, err := m.Process.MemoryInfo() + if err != nil { + log.Errorln(err) + return nil + } + ret := jsonutils.NewDict() + ret.Set("rss", jsonutils.NewInt(int64(mem.RSS))) + ret.Set("vms", jsonutils.NewInt(int64(mem.VMS))) + return ret +} diff --git a/pkg/hostman/hostutils/doc.go b/pkg/hostman/hostutils/doc.go new file mode 100644 index 0000000000..72e4caaa00 --- /dev/null +++ b/pkg/hostman/hostutils/doc.go @@ -0,0 +1 @@ +package hostutils // import "yunion.io/x/onecloud/pkg/hostman/hostutils" diff --git a/pkg/hostman/hostutils/hostutils.go b/pkg/hostman/hostutils/hostutils.go new file mode 100644 index 0000000000..3c7b73b8e3 --- /dev/null +++ b/pkg/hostman/hostutils/hostutils.go @@ -0,0 +1,134 @@ +package hostutils + +import ( + "context" + "fmt" + "net/http" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appctx" + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon/workmanager" + "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge" + "yunion.io/x/onecloud/pkg/hostman/isolated_device" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +type IHost interface { + GetZone() string + GetHostId() string + GetMediumType() string + + IsKvmSupport() bool + IsNestedVirtualization() bool + + PutHostOnline() error + + GetBridgeDev(bridge string) hostbridge.IBridgeDriver + GetIsolatedDeviceManager() *isolated_device.IsolatedDeviceManager +} + +func GetComputeSession(ctx context.Context) *mcclient.ClientSession { + return auth.GetAdminSession(ctx, options.HostOptions.Region, "v2") +} + +func GetImageSession(ctx context.Context, zone string) *mcclient.ClientSession { + return auth.AdminSession(ctx, options.HostOptions.Region, zone, "internal", "v1") +} + +func TaskFailed(ctx context.Context, reason string) { + if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil { + modules.ComputeTasks.TaskFailed2(GetComputeSession(ctx), taskId.(string), reason) + } else { + log.Errorf("Reqeuest task failed missing task id, with reason(%s)", reason) + } +} + +func TaskComplete(ctx context.Context, params jsonutils.JSONObject) { + if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil { + modules.ComputeTasks.TaskComplete(GetComputeSession(ctx), taskId.(string), params) + } else { + log.Errorln("Reqeuest task complete missing task id") + } +} + +func GetWireOfIp(ctx context.Context, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + res, err := modules.Networks.List(GetComputeSession(ctx), params) + if err != nil { + return nil, err + } + + if len(res.Data) == 1 { + wireId, _ := res.Data[0].GetString("wire_id") + return GetWireInfo(ctx, wireId) + } else { + return nil, fmt.Errorf("Fail to get network info: no networks") + } +} + +func GetWireInfo(ctx context.Context, wireId string) (jsonutils.JSONObject, error) { + return modules.Wires.Get(GetComputeSession(ctx), wireId, nil) +} + +func RemoteStoragecacheCacheImage(ctx context.Context, storagecacheId, imageId, status, spath string) (jsonutils.JSONObject, error) { + var query = jsonutils.NewDict() + query.Set("auto_create", jsonutils.JSONTrue) + var params = jsonutils.NewDict() + params.Set("status", jsonutils.NewString(status)) + params.Set("path", jsonutils.NewString(spath)) + return modules.Storagecachedimages.Update(GetComputeSession(ctx), + storagecacheId, imageId, query, params) +} + +func UpdateServerStatus(ctx context.Context, sid, status string) (jsonutils.JSONObject, error) { + var stats = jsonutils.NewDict() + stats.Set("status", jsonutils.NewString(status)) + return modules.Servers.PerformAction(GetComputeSession(ctx), sid, "status", stats) +} + +func ResponseOk(ctx context.Context, w http.ResponseWriter) { + Response(ctx, w, map[string]string{"result": "ok"}) +} + +func Response(ctx context.Context, w http.ResponseWriter, res interface{}) { + if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil { + w.Header().Set("X-Request-Id", taskId.(string)) + } + switch res.(type) { + case string: + appsrv.Send(w, res.(string)) + case jsonutils.JSONObject: + appsrv.SendJSON(w, res.(jsonutils.JSONObject)) + case error: + httperrors.GeneralServerError(w, res.(error)) + default: + appsrv.SendStruct(w, res) + } +} + +var ( + wm *workmanager.SWorkManager + ParamsError = fmt.Errorf("Delay task parse params error") +) + +func GetWorkManager() *workmanager.SWorkManager { + return wm +} + +func DelayTask(ctx context.Context, task workmanager.DelayTaskFunc, params interface{}) { + wm.DelayTask(ctx, task, params) +} + +func DelayTaskWithoutReqctx(ctx context.Context, task workmanager.DelayTaskFunc, params interface{}) { + wm.DelayTaskWithoutReqctx(ctx, task, params) +} + +func init() { + wm = workmanager.NewWorkManger(TaskFailed, TaskComplete) +} diff --git a/pkg/hostman/isolated_device/doc.go b/pkg/hostman/isolated_device/doc.go new file mode 100644 index 0000000000..ffc299e8a8 --- /dev/null +++ b/pkg/hostman/isolated_device/doc.go @@ -0,0 +1 @@ +package isolated_device // import "yunion.io/x/onecloud/pkg/hostman/isolated_device" diff --git a/pkg/hostman/isolated_device/isolated_device.go b/pkg/hostman/isolated_device/isolated_device.go new file mode 100644 index 0000000000..0bf974df2b --- /dev/null +++ b/pkg/hostman/isolated_device/isolated_device.go @@ -0,0 +1,778 @@ +package isolated_device + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/sets" + "yunion.io/x/pkg/utils" + + o "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/regutils2" +) + +const ( + // TODO: merge models/isolated_devices in new file + DIRECT_PCI_TYPE = "PCI" + GPU_HPC_TYPE = "GPU-HPC" // # for compute + GPU_VGA_TYPE = "GPU-VGA" // # for display + USB_TYPE = "USB" + + CLASS_CODE_VGA = "0300" + CLASS_CODE_3D = "0302" +) + +const ( + BUSID_REGEX = `[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]` + CODE_REGEX = `[0-9a-fA-F]{4}` + LABEL_REGEX = `[\w+\ \.\,\:\+\&\-\/\[\]\(\)]+` + + VFIO_PCI_KERNEL_DRIVER = "vfio-pci" + DEFAULT_VGA_CMD = " -vga std" + // 在qemu/kvm下模拟Windows Hyper-V的一些半虚拟化特性,以便更好地使用Win虚拟机 + // http://blog.wikichoon.com/2014/07/enabling-hyper-v-enlightenments-with-kvm.html + // 但实际测试不行,虚拟机不能运行nvidia驱动 + // DEFAULT_CPU_CMD = "host,kvm=off,hv_relaxed,hv_spinlocks=0x1fff,hv_vapic,hv_time" + DEFAULT_CPU_CMD = "host,kvm=off" + + RESOURCE = "isolated_devices" +) + +type CloudDeviceInfo struct { + Id string `json:"id"` + GuestId string `json:"guest_id"` + HostId string `json:"host_id"` + DevType string `json:"dev_type"` + VendorDeviceId string `json:"vendor_device_id"` + Addr string `json:"addr"` + DetectedOnHost bool `json:"detected_on_host"` +} + +type IHost interface { + GetHostId() string + GetSession() *mcclient.ClientSession +} + +type IDevice interface { + String() string + GetCloudId() string + GetVendorDeviceId() string + GetAddr() string + GetDeviceType() string + CustomProbe() error + SetDeviceInfo(info CloudDeviceInfo) + SetDetectedOnHost(isDetected bool) + + GetPassthroughCmd(index int) string + GetIOMMUGroupDeviceCmd() string + GetVGACmd() string + GetCPUCmd() string + SyncDeviceInfo(IHost) error +} + +type IsolatedDeviceManager struct { + host IHost + Devices []IDevice + DetachedDevices []*CloudDeviceInfo +} + +func NewManager(host IHost) (*IsolatedDeviceManager, error) { + man := &IsolatedDeviceManager{ + host: host, + Devices: make([]IDevice, 0), + DetachedDevices: make([]*CloudDeviceInfo, 0), + } + err := man.fillPCIDevices() + return man, err +} + +func (man *IsolatedDeviceManager) fillPCIDevices() error { + // only support gpu by now + gpus, err := getPassthroughGPUS() + if err != nil { + return fmt.Errorf("getPassthroughGPUS: %v", err) + } + for idx, gpu := range gpus { + man.Devices = append(man.Devices, newGPUHPCDevice(gpu)) + log.Infof("GPU device: %d => %#v", idx, gpu) + } + return nil +} + +func (man *IsolatedDeviceManager) getSession() *mcclient.ClientSession { + return man.host.GetSession() +} + +func (man *IsolatedDeviceManager) GetDeviceByIdent(vendorDevId string, addr string) IDevice { + for _, dev := range man.Devices { + if dev.GetVendorDeviceId() == vendorDevId && dev.GetAddr() == addr { + return dev + } + } + return nil +} + +func (man *IsolatedDeviceManager) GetDeviceByVendorDevId(vendorDevId string) IDevice { + for _, dev := range man.Devices { + if dev.GetVendorDeviceId() == vendorDevId { + return dev + } + } + return nil +} + +func (man *IsolatedDeviceManager) GetDeviceByAddr(addr string) IDevice { + for _, dev := range man.Devices { + if dev.GetAddr() == addr { + return dev + } + } + return nil +} + +func (man *IsolatedDeviceManager) BatchCustomProbe() error { + for _, dev := range man.Devices { + if err := dev.CustomProbe(); err != nil { + return err + } + } + return nil +} + +func (man *IsolatedDeviceManager) AppendDetachedDevice(dev *CloudDeviceInfo) { + dev.DetectedOnHost = false + man.DetachedDevices = append(man.DetachedDevices, dev) +} + +func (man *IsolatedDeviceManager) StartDetachTask() { + if len(man.DetachedDevices) == 0 { + return + } + go func() { + for _, dev := range man.DetachedDevices { + for { + if _, err := modules.IsolatedDevices.PerformAction(man.getSession(), dev.Id, "purge", nil); err != nil { + log.Errorf("Detach device %s failed: %v, try again later", dev.Id, err) + time.Sleep(30 * time.Second) + continue + } + break + } + } + man.DetachedDevices = nil + }() +} + +func (man *IsolatedDeviceManager) GetQemuParams(devAddrs []string) *QemuParams { + return getQemuParams(man, devAddrs) +} + +type sBaseDevice struct { + dev *PCIDevice + cloudId string + hostId string + guestId string + devType string + detectedOnHost bool +} + +func newBaseDevice(dev *PCIDevice) *sBaseDevice { + return &sBaseDevice{dev: dev} +} + +func (dev *sBaseDevice) String() string { + return dev.dev.String() +} + +func (dev *sBaseDevice) SetDeviceInfo(info CloudDeviceInfo) { + if len(info.Id) != 0 { + dev.cloudId = info.Id + } + if len(info.GuestId) != 0 { + dev.guestId = info.GuestId + } + if len(info.HostId) != 0 { + dev.hostId = info.HostId + } + if len(info.DevType) != 0 { + dev.devType = info.DevType + } +} + +func (dev *sBaseDevice) SetDetectedOnHost(probe bool) { + dev.detectedOnHost = probe +} + +func (dev *sBaseDevice) SyncDeviceInfo(host IHost) error { + if len(dev.hostId) == 0 { + dev.hostId = host.GetHostId() + } + data := dev.GetApiResourceData() + if len(dev.GetCloudId()) != 0 { + log.Infof("Update %s isolated_device: %s", dev.GetCloudId(), data.String()) + _, err := modules.IsolatedDevices.Update(host.GetSession(), dev.GetCloudId(), data) + return err + } + log.Infof("Create new isolated_device: %s", data.String()) + _, err := modules.IsolatedDevices.Create(host.GetSession(), data) + return err +} + +func (dev *sBaseDevice) GetCloudId() string { + return dev.cloudId +} + +func (dev *sBaseDevice) GetVendorDeviceId() string { + return dev.dev.GetVendorDeviceId() +} + +func (dev *sBaseDevice) GetAddr() string { + return dev.dev.Addr +} + +func (dev *sBaseDevice) GetDeviceType() string { + return dev.devType +} + +func (dev *sBaseDevice) GetApiResourceData() jsonutils.JSONObject { + data := map[string]interface{}{ + "dev_type": dev.GetDeviceType(), + "addr": dev.GetAddr(), + "model": dev.dev.ModelName, + "vendor_device_id": dev.GetVendorDeviceId(), + } + detected := false + if _, err := detectPCIDevByAddr(dev.GetAddr()); err == nil { + detected = true + } + data["detected_on_host"] = detected + if len(dev.cloudId) != 0 { + data["id"] = dev.cloudId + } + if len(dev.hostId) != 0 { + data["host_id"] = dev.hostId + } + if len(dev.guestId) != 0 { + data["guest_id"] = dev.guestId + } + return jsonutils.Marshal(data) +} + +func (dev *sBaseDevice) GetKernelDriver() (string, error) { + return dev.dev.getKernelDriver() +} + +func (dev *sBaseDevice) IsPassthroughAble() bool { + driver, _ := dev.GetKernelDriver() + return driver == VFIO_PCI_KERNEL_DRIVER +} + +func (dev *sBaseDevice) getVFIODeviceCmd(addr string) string { + return fmt.Sprintf(" -device vfio-pci,host=%s", addr) +} + +func (dev *sBaseDevice) GetPassthroughCmd(_ int) string { + return dev.getVFIODeviceCmd(dev.GetAddr()) +} + +func (dev *sBaseDevice) GetIOMMUGroupRestAddrs() []string { + return dev.dev.RestIOMMUGroupAddrs +} + +func (dev *sBaseDevice) GetIOMMUGroupDeviceCmd() string { + restAddrs := dev.GetIOMMUGroupRestAddrs() + cmds := []string{} + for _, addr := range restAddrs { + cmds = append(cmds, dev.getVFIODeviceCmd(addr)) + } + return strings.Join(cmds, "") +} + +type sGPUBaseDevice struct { + *sBaseDevice +} + +func newGPUBaseDevice(dev *PCIDevice) *sGPUBaseDevice { + return &sGPUBaseDevice{ + sBaseDevice: newBaseDevice(dev), + } +} + +func (dev *sGPUBaseDevice) GetCPUCmd() string { + return DEFAULT_CPU_CMD +} + +func (dev *sGPUBaseDevice) GetVGACmd() string { + return DEFAULT_VGA_CMD +} + +func (dev *sGPUBaseDevice) CustomProbe() error { + // vfio kernel driver check + for _, driver := range []string{"vfio", "vfio_iommu_type1", "vfio-pci"} { + if _, err := procutils.Run("modprobe", driver); err != nil { + return fmt.Errorf("modprobe %s: %v", driver, err) + } + } + // grub check + grubCmdline, err := fileutils2.FileGetContents("/proc/cmdline") + if err != nil { + return err + } + params := sets.NewString(strings.Split(grubCmdline, " ")...) + if !params.IsSuperset(sets.NewString("intel_iommu=on", + "vfio_iommu_type1.allow_unsafe_interrupts=1")) { + return fmt.Errorf("Some GRUB_CMDLINE iommu parameters are missing") + } + isNouveauBlacklisted := false + if params.IsSuperset(sets.NewString("rdblacklist=nouveau", "nouveau.modeset=0")) || + params.IsSuperset(sets.NewString("rd.driver.blacklist=nouveau", "nouveau.modeset=0")) { + isNouveauBlacklisted = true + } + if !isNouveauBlacklisted { + return fmt.Errorf("Some GRUB_CMDLINE nouveau_blacklisted parameters are missing") + } + driver, err := dev.GetKernelDriver() + if err != nil { + return err + } + if driver != "" && driver != VFIO_PCI_KERNEL_DRIVER { + return fmt.Errorf("GPU is occupied by another driver: %s", driver) + } + if driver == "" { + //fileutils2.FilePutContents( + //fmt.Sprintf("%s\n", strings.Replace(dev.GetVendorDeviceId(), ":", " ", -1)), + //false) + } + return nil +} + +type sGPUVGADevice struct { + *sGPUBaseDevice +} + +func (gpu *sGPUVGADevice) GetDeviceType() string { + return GPU_VGA_TYPE +} + +func (gpu *sGPUVGADevice) GetVGACmd() string { + return " -vga none" +} + +func getGuestAddr(index int) string { + vAddr := fmt.Sprintf("0x%x", 21+index) // from 0x15 above + return vAddr +} + +func (gpu *sGPUVGADevice) GetPassthroughCmd(index int) string { + vAddr := getGuestAddr(index) + return fmt.Sprintf(" -device vfio-pci,host=%s,multifunction=on,x-vga=on,addr=%s", gpu.GetAddr(), vAddr) +} + +func (gpu *sGPUVGADevice) CustomProbe() error { + _, err := bashOutput(`cat /boot/cfg-$(uname -r) | grep -E "^CONFIG_VFIO_PCI_VGA=y"`) + if err != nil { + return fmt.Errorf("CONFIG_VFIO_PCI_VGA=y needs to be set in kernel compiling parameters") + } + return nil +} + +type sGPUHPCDevice struct { + *sGPUBaseDevice +} + +func newGPUHPCDevice(dev *PCIDevice) *sGPUHPCDevice { + return &sGPUHPCDevice{ + sGPUBaseDevice: newGPUBaseDevice(dev), + } +} + +func (gpu *sGPUHPCDevice) GetDeviceType() string { + return GPU_HPC_TYPE +} + +func (gpu *sGPUHPCDevice) GetPassthroughCmd(index int) string { + vAddr := getGuestAddr(index) + return fmt.Sprintf(" -device vfio-pci,host=%s,multifunction=on,addr=%s", gpu.GetAddr(), vAddr) +} + +func bashOutput(cmd string) ([]string, error) { + args := []string{"-c", cmd} + return procutils.Run("bash", args...) +} + +func gpuPCIString() ([]string, error) { + lines, err := bashOutput("lspci -nnmm | egrep '3D|VGA'") + if err != nil { + return nil, fmt.Errorf("Get GPU PCI: %v", err) + } + ret := []string{} + for _, line := range lines { + if len(line) != 0 { + ret = append(ret, line) + } + } + return ret, nil +} + +func gpuPCIAddr() ([]string, error) { + lines, err := gpuPCIString() + if err != nil { + return nil, err + } + addrs := []string{} + for _, line := range lines { + addrs = append(addrs, strings.Split(line, " ")[0]) + } + return addrs, nil +} + +type PCIDevice struct { + Addr string `json:"bus_id"` + ClassName string `json:"class_name"` + ClassCode string `json:"class_code"` + VendorName string `json:"vendor_name"` + VendorId string `json:"vendor_id"` + DeviceName string `json:"device_name"` + DeviceId string `json:"device_id"` + SubvendorName string `json:"subvendor_name"` + SubvendorId string `json:"subvendor_id"` + SubdeviceName string `json:"subdevice_name"` + SubdeviceId string `json:"subdevice_id"` + ModelName string `json:"model_name"` + + RestIOMMUGroupAddrs []string `json:"-"` +} + +func NewPCIDevice(line string) (*PCIDevice, error) { + dev := parseLspci(line) + if err := dev.checkSameIOMMUGroupDevice(); err != nil { + return nil, err + } + if err := dev.forceBindVFIOPCIDriver(o.HostOptions.UseBootVga); err != nil { + return nil, fmt.Errorf("Force bind vfio-pci driver: %v", err) + } + return dev, nil +} + +// parseLspci parse one line output of `lspci -nnmm` +func parseLspci(line string) *PCIDevice { + itemRegex := `(?P(` + BUSID_REGEX + `))` + + `\ "(?P` + LABEL_REGEX + `)\ \[(?P` + CODE_REGEX + `)\]"` + + `\ "(?P` + LABEL_REGEX + `)\ \[(?P` + CODE_REGEX + `)\]"` + + `\ "(?P` + LABEL_REGEX + `)\ \[(?P` + CODE_REGEX + `)\]"` + + `\ .*\"((?P` + LABEL_REGEX + `)\ \[(?P` + CODE_REGEX + `)\])*"` + + `\ "((?P` + LABEL_REGEX + `)\ \[(?P` + CODE_REGEX + `)\])*` + ret := regutils2.SubGroupMatch(itemRegex, line) + dev := PCIDevice{} + jsonutils.Marshal(ret).Unmarshal(&dev) + deviceRegex := `(?P` + LABEL_REGEX + `)\ \[(?P` + LABEL_REGEX + `)\]` + if ret := regutils2.SubGroupMatch(deviceRegex, dev.DeviceName); len(ret) != 0 { + dev.ModelName = ret["model_name"] + } + return &dev +} + +func (d *PCIDevice) GetVendorDeviceId() string { + return fmt.Sprintf("%s:%s", d.VendorId, d.DeviceId) +} + +// checkSameIOMMUGroupDevice check related device like Audio in same iommu group +// e.g. +// 41:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP107 [GeForce GTX 1050 Ti] [10de:1c82] (rev a1) +// 41:00.1 Audio device [0403]: NVIDIA Corporation GP107GL High Definition Audio Controller [10de:0fb9] (rev a1) +func (d *PCIDevice) checkSameIOMMUGroupDevice() error { + group, err := NewIOMMUGroup() + if err != nil { + return fmt.Errorf("IOMMUGroup FindSameGroupAddrs: %v", err) + } + removeAddr := func(addrs []string, addr string) []string { + ret := []string{} + for _, tmpAddr := range addrs { + if tmpAddr == addr { + continue + } + ret = append(ret, tmpAddr) + } + return ret + } + groupAddrs := group.FindSameGroupAddrs(d.Addr, true) + if utils.IsInStringArray(d.Addr, groupAddrs) { + groupAddrs = removeAddr(groupAddrs, d.Addr) + } + d.RestIOMMUGroupAddrs = groupAddrs + return nil +} + +func (d *PCIDevice) IsBootVGA() (bool, error) { + addr := d.Addr + paths, err := procutils.Run("find", "/sys/devices", "-name", "boot_vga") + if err != nil { + return false, err + } + for _, p := range paths { + if strings.Contains(p, addr) { + if content, err := fileutils2.FileGetContents(p); err != nil { + return false, err + } else { + if len(content) > 0 && strings.HasPrefix(content, "1") { + log.Infof("PCI address %s is boot_vga: %s", addr, p) + return true, nil + } + } + } + } + return false, nil +} + +func (d *PCIDevice) forceBindVFIOPCIDriver(useBootVGA bool) error { + if !utils.IsInStringArray(d.ClassCode, []string{CLASS_CODE_VGA, CLASS_CODE_VGA}) { + return nil + } + isBootVGA, err := d.IsBootVGA() + if err != nil { + return err + } + if !useBootVGA && isBootVGA { + log.Infof("%#v is boot vga card, skip it", d) + return nil + } + if d.IsVFIOPCIDriverUsed() { + log.Infof("%s already use vfio-pci driver", d) + return nil + } + addrs := []string{} + addrs = append(addrs, d.RestIOMMUGroupAddrs...) + addrs = append(addrs, d.Addr) + for _, addr := range addrs { + if err := d.bindAddrVFIOPCI(addr); err != nil { + return fmt.Errorf("bind %s vfio-pci driver: %v", addr, err) + } + } + return nil +} + +func (d *PCIDevice) bindAddrVFIOPCI(addr string) error { + if err := d.unbindDriver(addr); err != nil { + return fmt.Errorf("unbindDriver: %v", err) + } + if err := d.bindDriver(); err != nil { + return fmt.Errorf("bindDriver: %v", err) + } + return nil +} + +func (d *PCIDevice) unbindDriver(addr string) error { + driver, err := d.getKernelDriver() + if err != nil { + return err + } + if len(driver) != 0 { + if err := fileutils2.FilePutContents( + fmt.Sprintf("/sys/bus/pci/devices/0000:%s/driver/unbind", addr), + fmt.Sprintf("0000:%s", addr), false); err != nil { + return fmt.Errorf("unbindDriver: %v", err) + } + } + return nil +} + +func (d *PCIDevice) bindDriver() error { + vendorDevId := fmt.Sprintf("%s %s", d.VendorId, d.DeviceId) + return fileutils2.FilePutContents( + "/sys/bus/pci/drivers/vfio-pci/new_id", + fmt.Sprintf("%s\n", vendorDevId), + false, + ) +} + +func (d *PCIDevice) String() string { + return jsonutils.Marshal(d).String() +} + +func (d *PCIDevice) IsVFIOPCIDriverUsed() bool { + driver, _ := d.getKernelDriver() + return driver == VFIO_PCI_KERNEL_DRIVER +} + +func (d *PCIDevice) getKernelDriver() (string, error) { + prompt := "Kernel driver in use: " + lines, err := bashOutput(fmt.Sprintf("lspci -k -s %s", d.Addr)) + if err != nil { + return "", err + } + for _, line := range lines { + begin := strings.Index(line, prompt) + if begin >= 0 { + end := begin + len(prompt) + return line[end : len(line)-1], nil + } + } + // no driver in use + return "", nil +} + +type IOMMUGroup struct { + // busId: group + group map[string]string +} + +func NewIOMMUGroup() (*IOMMUGroup, error) { + devPaths := "/sys/kernel/iommu_groups/" + dict := make(map[string]string) + err := filepath.Walk(devPaths, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + parts := strings.Split(path, "/") + group := parts[4] + busId := parts[len(parts)-1] + dict[busId] = group + return nil + }) + if err != nil { + return nil, err + } + return &IOMMUGroup{group: dict}, nil +} + +func (g *IOMMUGroup) ListDeviceAddrs(groupNum string, useShortFormat bool) []string { + ret := []string{} + for busId, group := range g.group { + if groupNum == group { + ret = append(ret, busId) + } + } + sort.Strings(ret) + if useShortFormat { + for idx, addr := range ret { + ret[idx] = addr[5:] + } + } + return ret +} + +func (g *IOMMUGroup) FindSameGroupAddrs(devAddr string, useShortFormat bool) []string { + // devAddr: '0000:3f:0f.3' or '3f:0f.3' format + if len(devAddr) == 7 { + devAddr = fmt.Sprintf("0000:%s", devAddr) + } + group, ok := g.group[devAddr] + if !ok { + return nil + } + return g.ListDeviceAddrs(group, useShortFormat) +} + +func (g *IOMMUGroup) String() string { + return jsonutils.Marshal(g.group).PrettyString() +} + +func getGPUPCIStr() ([]string, error) { + ret, err := bashOutput("lspci -nnmm | egrep '3D|VGA'") + if err != nil { + return nil, err + } + lines := []string{} + for _, l := range ret { + if len(l) != 0 { + lines = append(lines, l) + } + } + return lines, err +} + +func detectPCIDevByAddr(addr string) (*PCIDevice, error) { + ret, err := bashOutput(fmt.Sprintf("lspci -nnmm -s %s", addr)) + if err != nil { + return nil, err + } + return NewPCIDevice(strings.Join(ret, "")) +} + +func detectGPUS() ([]*PCIDevice, error) { + lines, err := getGPUPCIStr() + if err != nil { + return nil, err + } + devs := []*PCIDevice{} + for _, line := range lines { + dev, err := NewPCIDevice(line) + if err != nil { + return nil, err + } + devs = append(devs, dev) + } + return devs, nil +} + +func getPassthroughGPUS() ([]*PCIDevice, error) { + gpus, err := detectGPUS() + if err != nil { + return nil, err + } + ret := []*PCIDevice{} + for _, dev := range gpus { + if drv, err := dev.getKernelDriver(); err != nil { + log.Errorf("Device %#v get kernel driver error: %v", dev, err) + } else if drv == VFIO_PCI_KERNEL_DRIVER { + ret = append(ret, dev) + } + } + return ret, nil +} + +type QemuParams struct { + Cpu string + Vga string + Devices []string +} + +func GetDeviceCmd(dev IDevice, index int) string { + passthroughCmd := dev.GetPassthroughCmd(index) + groupDevCmd := dev.GetIOMMUGroupDeviceCmd() + if len(groupDevCmd) != 0 { + passthroughCmd = fmt.Sprintf("%s%s", passthroughCmd, groupDevCmd) + } + return passthroughCmd +} + +func getQemuParams(man *IsolatedDeviceManager, devAddrs []string) *QemuParams { + if len(devAddrs) == 0 { + return nil + } + devCmds := []string{} + cpuCmd := DEFAULT_CPU_CMD + vgaCmd := DEFAULT_VGA_CMD + for idx, addr := range devAddrs { + dev := man.GetDeviceByAddr(addr) + if dev == nil { + log.Warningf("IsolatedDeviceManager not found dev %#v, ignore it!", addr) + continue + } + devCmds = append(devCmds, GetDeviceCmd(dev, idx)) + if dev.GetVGACmd() != vgaCmd && dev.GetDeviceType() == GPU_VGA_TYPE { + vgaCmd = dev.GetVGACmd() + } + if dev.GetCPUCmd() != cpuCmd { + cpuCmd = dev.GetCPUCmd() + } + } + return &QemuParams{ + Cpu: cpuCmd, + Vga: vgaCmd, + Devices: devCmds, + } +} diff --git a/pkg/hostman/isolated_device/isolated_device_test.go b/pkg/hostman/isolated_device/isolated_device_test.go new file mode 100644 index 0000000000..b5061efb79 --- /dev/null +++ b/pkg/hostman/isolated_device/isolated_device_test.go @@ -0,0 +1,61 @@ +package isolated_device + +import ( + "reflect" + "testing" +) + +func Test_parseLspci(t *testing.T) { + type args struct { + line string + } + tests := []struct { + name string + args args + want *PCIDevice + }{ + { + name: "3D controller", + args: args{`02:00.0 "3D controller [0302]" "NVIDIA Corporation [10de]" "GM108M [GeForce 940MX] [134d]" -ra2 "Lenovo [17aa]" "GM108M [GeForce 940MX] [505e]`}, + want: &PCIDevice{ + Addr: "02:00.0", + ClassName: "3D controller", + ClassCode: "0302", + VendorName: "NVIDIA Corporation", + VendorId: "10de", + DeviceName: "GM108M [GeForce 940MX]", + DeviceId: "134d", + SubvendorName: "Lenovo", + SubvendorId: "17aa", + SubdeviceName: "GM108M [GeForce 940MX]", + SubdeviceId: "505e", + ModelName: "GeForce 940MX", + }, + }, + { + name: "VGA", + args: args{`05:00.0 "VGA compatible controller [0300]" "Advanced Micro Devices, Inc. [AMD/ATI] [1002]" "Oland [Radeon HD 8570 / R7 240/340 OEM] [6611]" "Dell [1028]" "Radeon R5 240 OEM [210b]"`}, + want: &PCIDevice{ + Addr: "05:00.0", + ClassName: "VGA compatible controller", + ClassCode: "0300", + VendorName: "Advanced Micro Devices, Inc. [AMD/ATI]", + VendorId: "1002", + DeviceName: "Oland [Radeon HD 8570 / R7 240/340 OEM]", + DeviceId: "6611", + SubvendorName: "Dell", + SubvendorId: "1028", + SubdeviceName: "Radeon R5 240 OEM", + SubdeviceId: "210b", + ModelName: "Radeon HD 8570 / R7 240/340 OEM", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseLspci(tt.args.line); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseLspci() = %#v, want %#v", got, tt.want) + } + }) + } +} diff --git a/pkg/hostman/kubehandler.go b/pkg/hostman/kubehandler.go new file mode 100644 index 0000000000..96fe71e994 --- /dev/null +++ b/pkg/hostman/kubehandler.go @@ -0,0 +1,139 @@ +package hostman + +import ( + "context" + "fmt" + "net/http" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/system_service" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/auth" +) + +type sKubeConf struct { + DockerdConf map[string]interface{} + AgentConfig map[string]interface{} +} + +var keyWords = []string{"kubeagent"} + +func addKubeAgentHandler(prefix string, app *appsrv.Application) { + for _, keyword := range keyWords { + app.AddHandler("POST", fmt.Sprintf("%s/%s/", prefix, keyword), + auth.Authenticate(dispatcher)) + } +} + +func dispatcher(ctx context.Context, w http.ResponseWriter, r *http.Request) { + var ( + params, _, body = appsrv.FetchEnv(ctx, w, r) + action = params[""] + ) + switch action { + case "start": + dockerdConf, _ := body.Get("dockerdConfig") + agentConfig, _ := body.Get("agentConfig") + err := prepareAgentStart(dockerdConf, agentConfig) + if err != nil { + hostutils.Response(ctx, w, err) + return + } + var dm = map[string]interface{}{} + if err := dockerdConf.Unmarshal(&dm); err != nil { + hostutils.Response(ctx, w, err) + return + } + + var am = map[string]interface{}{} + if err := agentConfig.Unmarshal(&am); err != nil { + hostutils.Response(ctx, w, err) + return + } + + hostutils.DelayTask(ctx, startAgent, &sKubeConf{dm, am}) + case "restart": + hostutils.DelayTask(ctx, restartAgent, nil) + case "stop": + hostutils.DelayTask(ctx, stopAgent, nil) + default: + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Not found")) + return + } + hostutils.ResponseOk(ctx, w) +} + +func prepareAgentStart(dockerdConf, agentConfig jsonutils.JSONObject) error { + if !agentConfig.Contains("serverUrl") { + return httperrors.NewBadRequestError("Kube server url empty") + } + if !agentConfig.Contains("nodeId") { + return httperrors.NewBadRequestError("NodeId empty") + } + if !agentConfig.Contains("token") { + return httperrors.NewBadRequestError("Register token empty") + } + return nil +} + +func startAgent(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + sp, ok := params.(*sKubeConf) + if !ok { + return nil, hostutils.ParamsError + } + lxcfs := system_service.GetService("lxcfs") + if !lxcfs.IsInstalled() { + return nil, fmt.Errorf("Service lxcfs not installed") + } else if !lxcfs.IsActive() { + if err := lxcfs.Start(false); err != nil { + return nil, err + } + } + if err := lxcfs.Enable(); err != nil { + return nil, err + } + if err := serviceReloadStart("docker", sp.DockerdConf); err != nil { + return nil, err + } + if err := serviceReloadStart("kube_agent", sp.AgentConfig); err != nil { + return nil, err + } + return nil, nil +} + +func serviceReloadStart(srv string, conf map[string]interface{}) error { + srvinst := system_service.GetService(srv) + if srvinst == nil { + return fmt.Errorf("srv %s not found", srv) + } + if !srvinst.IsInstalled() { + return fmt.Errorf("Service %s nout found", srv) + } + if err := srvinst.Reload(conf); err != nil { + return err + } + if !srvinst.IsActive() { + if err := srvinst.Start(false); err != nil { + return err + } + } + return srvinst.Enable() +} + +func restartAgent(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + srvinst := system_service.GetService("kube_agent") + if !srvinst.IsInstalled() { + return nil, fmt.Errorf("Service kube_agent not installed") + } + return nil, srvinst.Start(true) +} + +func stopAgent(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + srvinst := system_service.GetService("kube_agent") + if !srvinst.IsInstalled() { + return nil, fmt.Errorf("Service kube_agent not installed") + } + return nil, srvinst.Stop(true) +} diff --git a/pkg/hostman/monitor/doc.go b/pkg/hostman/monitor/doc.go new file mode 100644 index 0000000000..e9c9aa9b66 --- /dev/null +++ b/pkg/hostman/monitor/doc.go @@ -0,0 +1 @@ +package monitor // import "yunion.io/x/onecloud/pkg/hostman/monitor" diff --git a/pkg/hostman/monitor/hmp.go b/pkg/hostman/monitor/hmp.go new file mode 100644 index 0000000000..af21ce9d21 --- /dev/null +++ b/pkg/hostman/monitor/hmp.go @@ -0,0 +1,358 @@ +package monitor + +import ( + "bufio" + "bytes" + "fmt" + "io" + "regexp" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" +) + +type HmpMonitor struct { + SBaseMonitor + + commandQueue []string + callbackQueue []StringCallback +} + +func NewHmpMonitor(OnMonitorDisConnect, OnMonitorTimeout MonitorErrorFunc, OnMonitorConnected MonitorSuccFunc) *HmpMonitor { + return &HmpMonitor{ + SBaseMonitor: *NewBaseMonitor(OnMonitorConnected, OnMonitorDisConnect, OnMonitorTimeout), + commandQueue: make([]string, 0), + callbackQueue: make([]StringCallback, 0), + } +} + +var hmpMark = []byte("(qemu) ") + +func (m *HmpMonitor) hmpSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + index := bytes.Index(data, hmpMark) + if index >= 0 { + return index + len(hmpMark), data[0:index], nil + } + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} + +func (m *HmpMonitor) actionResult(res string) string { + return res +} + +func (m *HmpMonitor) read(r io.Reader) { + if !m.checkReading() { + return + } + scanner := bufio.NewScanner(r) + scanner.Split(m.hmpSplitFunc) + for scanner.Scan() { + res := scanner.Text() + if len(res) == 0 { + continue + } + if m.connected { + go m.callBack(res) + } else { + // remove reader timeout + m.connected = true + m.rwc.SetReadDeadline(time.Time{}) + } + } + log.Errorln("Scan over ...") + if err := scanner.Err(); err != nil { + log.Errorln(err) + if m.connected { + m.connected = false + m.OnMonitorDisConnect(err) + } else { + m.OnMonitorTimeout(err) + } + } + m.reading = false +} + +func (m *HmpMonitor) callBack(res string) { + m.mutex.Lock() + if len(m.callbackQueue) == 0 { + return + } + cb := m.callbackQueue[0] + m.callbackQueue = m.callbackQueue[1:] + m.mutex.Unlock() + if cb != nil { + go cb(res) + } +} + +func (m *HmpMonitor) write(cmd []byte) error { + cmd = append(cmd, '\n') + length, index := len(cmd), 0 + for index < length { + i, err := m.rwc.Write(cmd) + if err != nil { + return err + } + index += i + } + return nil +} + +func (m *HmpMonitor) query() { + if !m.checkWriting() { + return + } + for { + if len(m.commandQueue) == 0 { + break + } + //pop + m.mutex.Lock() + cmd := m.commandQueue[0] + m.commandQueue = m.commandQueue[1:] + err := m.write([]byte(cmd)) + m.mutex.Unlock() + if err != nil { + log.Errorf("Write %s to monitor error: %s", cmd, err) + break + } + } + m.writing = false +} + +func (m *HmpMonitor) Query(cmd string, cb StringCallback) { + // push + m.mutex.Lock() + m.commandQueue = append(m.commandQueue, cmd) + m.callbackQueue = append(m.callbackQueue, cb) + m.mutex.Unlock() + if m.connected { + if !m.writing { + go m.query() + } + if !m.reading { + go m.read(m.rwc) + } + } + +} + +func (m *HmpMonitor) Connect(host string, port int) error { + err := m.SBaseMonitor.Connect(host, port) + if err != nil { + return err + } + go m.read(m.rwc) + return nil +} + +func (m *HmpMonitor) QueryStatus(callback StringCallback) { + m.Query("info status", m.parseStatus(callback)) +} + +func (m *HmpMonitor) parseStatus(callback StringCallback) StringCallback { + return func(res string) { + strs := strings.Split(res, "\r\n") + for _, str := range strs { + if strings.HasPrefix(str, "VM status:") { + callback(strings.TrimSpace(str[len("VM status:"):])) + return + } + } + } +} + +func (m *HmpMonitor) SimpleCommand(cmd string, callback StringCallback) { + m.Query(cmd, callback) +} + +func (m *HmpMonitor) HumanMonitorCommand(cmd string, callback StringCallback) { + m.Query(cmd, callback) +} + +func (m *HmpMonitor) GetVersion(callback StringCallback) { + m.Query("info version", callback) +} + +func (m *HmpMonitor) GetBlocks(callback func(*jsonutils.JSONArray)) { + var cb = func(res string) { + var lines = strings.Split(res, "\r\n") + var mergedOutput = []string{} + + // merge output + for _, line := range lines { + parts := regexp.MustCompile(`\s+`).Split(line, -1) + if parts[0][len(parts[0])-1] == ':' { + mergedOutput = append(mergedOutput, "") + } else if regexp.MustCompile(`\(#block\d+\):`).MatchString(line) { + mergedOutput = append(mergedOutput, "") + } + mergedOutput[len(mergedOutput)-1] = mergedOutput[len(mergedOutput)-1] + " " + line + mergedOutput[len(mergedOutput)-1] = strings.TrimSpace(mergedOutput[len(mergedOutput)-1]) + } + + // parse to json + var outputJson = jsonutils.NewArray() + for _, line := range mergedOutput { + parts := regexp.MustCompile(`\s+`).Split(line, -1) + if parts[0][len(parts[0])-1] == ':' || + regexp.MustCompile(`\(#block\d+\):`).MatchString(parts[1]) { + + drv := jsonutils.NewDict() + drv.Set("device", jsonutils.NewString(parts[0])) + if parts[0][len(parts[0])-1] == ':' { + drv.Set("device", jsonutils.NewString(parts[0][:len(parts)-1])) + } + if regexp.MustCompile(`\(#block\d+\):`).MatchString(parts[1]) { + inserted := jsonutils.NewDict() + inserted.Set("file", jsonutils.NewString(parts[2])) + for i := 0; i < len(parts)-2; i++ { + if parts[i] == "Backing" && parts[i+1] == "file:" { + inserted.Set("backing_file", jsonutils.NewString(parts[i+2])) + break + } + } + drv.Set("inserted", inserted) + } + outputJson.Add(drv) + } + } + + callback(outputJson) + } + + m.Query("info block", cb) +} + +func (m *HmpMonitor) EjectCdrom(dev string, callback StringCallback) { + m.Query(fmt.Sprintf("eject -f %s", dev), callback) +} + +func (m *HmpMonitor) ChangeCdrom(dev string, path string, callback StringCallback) { + m.Query(fmt.Sprintf("change %s %s", dev, path), callback) +} + +func (m *HmpMonitor) DriveDel(idstr string, callback StringCallback) { + m.Query(fmt.Sprintf("drive_del %s", idstr), callback) +} + +func (m *HmpMonitor) DeviceDel(idstr string, callback StringCallback) { + m.Query(fmt.Sprintf("device_del %s", idstr), callback) +} + +func (m *HmpMonitor) DriveAdd(bus string, params map[string]string, callback StringCallback) { + var paramsKvs = []string{} + for k, v := range params { + paramsKvs = append(paramsKvs, fmt.Sprintf("%s=%s", k, v)) + } + m.Query(fmt.Sprintf("drive_add %s %s", bus, strings.Join(paramsKvs, ",")), callback) +} + +func (m *HmpMonitor) DeviceAdd(dev string, params map[string]interface{}, callback StringCallback) { + var paramsKvs = []string{} + for k, v := range params { + paramsKvs = append(paramsKvs, fmt.Sprintf("%s=%v", k, v)) + } + m.Query(fmt.Sprintf("device_add %s,%s", dev, strings.Join(paramsKvs, ",")), callback) +} + +func (m *HmpMonitor) MigrateSetCapability(capability, state string, callback StringCallback) { + m.Query(fmt.Sprintf("migrate_set_capability %s %s", capability, state), callback) +} + +func (m *HmpMonitor) Migrate( + destStr string, copyIncremental, copyFull bool, callback StringCallback, +) { + cmd := "migrate -d" + if copyIncremental { + cmd += " -i" + } else if copyFull { + cmd += " -b" + } + cmd += " " + destStr + m.Query(cmd, callback) +} + +func (m *HmpMonitor) GetMigrateStatus(callback StringCallback) { + cb := func(output string) { + log.Infof("Query migrate status: %s", output) + + var status string + for _, line := range strings.Split(output, "\n") { + if strings.HasPrefix(line, "Migration status") { + status = line[strings.LastIndex(line, " ")+1:] + break + } + } + callback(status) + } + + m.Query("info migrate", cb) +} + +func (m *HmpMonitor) GetBlockJobs(callback func(jobs int)) { + cb := func(output string) { + lines := strings.Split(output, "\n") + if lines[0] == "No active jobs" { + callback(0) + } else { + callback(len(lines)) + } + } + + m.Query("info block-jobs", cb) +} + +func (m *HmpMonitor) ReloadDiskBlkdev(device, path string, callback StringCallback) { + m.Query(fmt.Sprintf("reload_disk_snapshot_blkdev -n %s %s", device, path), callback) +} + +func (m *HmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode string, unmap bool) { + cmd := "drive_mirror -n" + if syncMode == "full" { + cmd += " -f" + } + cmd += fmt.Sprintf(" %s %s", drive, target) + m.Query(cmd, callback) +} + +func (m *HmpMonitor) BlockStream(drive string, callback StringCallback) { + var ( + speed = 30 // MB/s + cmd = fmt.Sprintf("block_stream %s %d", drive, speed) + ) + m.Query(cmd, callback) +} + +func (m *HmpMonitor) SetVncPassword(proto, password string, callback StringCallback) { + if len(password) > 8 { + password = password[:8] + } + m.Query(fmt.Sprintf("set_password %s %s", proto, password), callback) +} + +func (m *HmpMonitor) StartNbdServer(port int, exportAllDevice, writable bool, callback StringCallback) { + var cmd = "nbd_server_start" + if exportAllDevice { + cmd += " -a" + } + if writable { + cmd += " -w" + } + cmd += fmt.Sprintf(" 0.0.0.0:%d", port) + m.Query(cmd, callback) +} + +func (m *HmpMonitor) ResizeDisk(driveName string, sizeMB int64, callback StringCallback) { + cmd := fmt.Sprintf("block_resize %s %d", driveName, sizeMB) + m.Query(cmd, callback) +} diff --git a/pkg/hostman/monitor/hmp_test.go b/pkg/hostman/monitor/hmp_test.go new file mode 100644 index 0000000000..b00ad76a9e --- /dev/null +++ b/pkg/hostman/monitor/hmp_test.go @@ -0,0 +1,26 @@ +package monitor + +import ( + "testing" + "time" + + "yunion.io/x/log" +) + +func TestHmpMonitor_Connect(t *testing.T) { + onConnected := func() { log.Infof("Monitor Connected") } + onDisConnect := func(error) { log.Infof("Monitor DisConnect") } + onTimeout := func(error) { log.Infof("Monitor Timeout") } + m := NewHmpMonitor(onDisConnect, onTimeout, onConnected) + var host = "127.0.0.1" + var port = 55901 + m.Connect(host, port) + rawCallBack := func(res string) { log.Infof("OnCallback: %s", res) } + m.Query("info block", rawCallBack) + m.Query("unknown cmd", rawCallBack) + + statusCallBack := func(res string) { log.Infof("OnStatusCallback %s", res) } + m.QueryStatus(statusCallBack) + m.Disconnect() + time.Sleep(3 * time.Second) +} diff --git a/pkg/hostman/monitor/monitor.go b/pkg/hostman/monitor/monitor.go new file mode 100644 index 0000000000..4573de4d4e --- /dev/null +++ b/pkg/hostman/monitor/monitor.go @@ -0,0 +1,124 @@ +package monitor + +import ( + "fmt" + "net" + "sync" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" +) + +type StringCallback func(string) + +type Monitor interface { + Connect(host string, port int) error + Disconnect() + IsConnected() bool + + // The callback function will be called in another goroutine + SimpleCommand(cmd string, callback StringCallback) + HumanMonitorCommand(cmd string, callback StringCallback) + + QueryStatus(StringCallback) + GetVersion(StringCallback) + GetBlockJobs(func(jobs int)) + + GetBlocks(callback func(*jsonutils.JSONArray)) + EjectCdrom(dev string, callback StringCallback) + ChangeCdrom(dev string, path string, callback StringCallback) + + DriveDel(idstr string, callback StringCallback) + DeviceDel(idstr string, callback StringCallback) + + DriveAdd(bus string, params map[string]string, callback StringCallback) + DeviceAdd(dev string, params map[string]interface{}, callback StringCallback) + + BlockStream(drive string, callback StringCallback) + DriveMirror(callback StringCallback, drive, target, syncMode string, unmap bool) + + MigrateSetCapability(capability, state string, callback StringCallback) + Migrate(destStr string, copyIncremental, copyFull bool, callback StringCallback) + GetMigrateStatus(callback StringCallback) + + ReloadDiskBlkdev(device, path string, callback StringCallback) + SetVncPassword(proto, password string, callback StringCallback) + StartNbdServer(port int, exportAllDevice, writable bool, callback StringCallback) + + ResizeDisk(driveName string, sizeMB int64, callback StringCallback) +} + +type MonitorErrorFunc func(error) +type MonitorSuccFunc func() + +type SBaseMonitor struct { + OnMonitorDisConnect MonitorErrorFunc + OnMonitorConnected MonitorSuccFunc + OnMonitorTimeout MonitorErrorFunc + + QemuVersion string + connected bool + timeout bool + rwc net.Conn + + mutex *sync.Mutex + writing bool + reading bool +} + +func NewBaseMonitor(OnMonitorConnected MonitorSuccFunc, OnMonitorDisConnect, OnMonitorTimeout MonitorErrorFunc) *SBaseMonitor { + return &SBaseMonitor{ + OnMonitorConnected: OnMonitorConnected, + OnMonitorDisConnect: OnMonitorDisConnect, + OnMonitorTimeout: OnMonitorTimeout, + timeout: true, + mutex: &sync.Mutex{}, + } +} + +func (m *SBaseMonitor) Connect(host string, port int) error { + address := fmt.Sprintf("%s:%d", host, port) + conn, err := net.Dial("tcp", address) + if err != nil { + log.Errorln("Connect monitor error:%s", err) + return err + } + // Setup reader timeout + conn.SetReadDeadline(time.Now().Add(90 * time.Second)) + m.rwc = conn + return nil +} + +func (m *SBaseMonitor) Disconnect() { + if m.connected { + m.connected = false + m.rwc.Close() + } +} + +func (m *SBaseMonitor) IsConnected() bool { + return m.connected +} + +func (m *SBaseMonitor) checkReading() bool { + m.mutex.Lock() + defer m.mutex.Unlock() + if m.reading { + return false + } else { + m.reading = true + } + return true +} + +func (m *SBaseMonitor) checkWriting() bool { + m.mutex.Lock() + defer m.mutex.Unlock() + if m.writing { + return false + } else { + m.writing = true + } + return true +} diff --git a/pkg/hostman/monitor/qmp.go b/pkg/hostman/monitor/qmp.go new file mode 100644 index 0000000000..83705d2663 --- /dev/null +++ b/pkg/hostman/monitor/qmp.go @@ -0,0 +1,705 @@ +package monitor + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "regexp" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" +) + +// https://github.com/qemu/qemu/blob/master/docs/interop/qmp-spec.txt +// https://wiki.qemu.org/QMP +/* +Not support oob yet +1. response error message + { "error": { "class": json-string, "desc": json-string }, "id": json-value } +2. response event message + { "event": json-string, "data": json-object, + "timestamp": { "seconds": json-number, "microseconds": json-number } } +3. response cmd result + { "return": json-value, "id": json-value } +4. response qmp init information + { "QMP": {"version": {"qemu": {"micro": 0, "minor": 0, "major": 3}, + "package": "v3.0.0"}, "capabilities": [] } } +*/ + +type qmpMonitorCallBack func(*Response) +type qmpEventCallback func(*Event) + +type Response struct { + Return []byte + ErrorVal *Error + Id string +} + +type Event struct { + Event string `json:"event"` + Data map[string]interface{} `json:"data"` + Timestamp *Timestamp `json:"timestamp"` +} + +func (e *Event) String() string { + return fmt.Sprintf("QMP Event result: %#v", e) +} + +type Timestamp struct { + Seconds int64 `json:"seconds"` + Microsenconds int64 `json:"microsenconds"` +} + +type Command struct { + Execute string `json:"execute"` + Args interface{} `json:"arguments,omitempty"` +} + +type Version struct { + Package string `json:"package"` + QEMU struct { + Major int `json:"major"` + Micro int `json:"micro"` + Minor int `json:"minor"` + } `json:"qemu"` +} + +func (v *Version) String() string { + q := v.QEMU + return fmt.Sprintf("%d.%d.%d", q.Major, q.Minor, q.Micro) +} + +type Error struct { + Class string `json:"class"` + Desc string `json:"desc"` +} + +func (e *Error) Error() string { + return fmt.Sprintf("%s: %s", e.Class, e.Desc) +} + +type QmpMonitor struct { + SBaseMonitor + + qmpEventFunc qmpEventCallback + commandQueue []*Command + callbackQueue []qmpMonitorCallBack +} + +func NewQmpMonitor(OnMonitorDisConnect, OnMonitorTimeout MonitorErrorFunc, + OnMonitorConnected MonitorSuccFunc, qmpEventFunc qmpEventCallback) *QmpMonitor { + m := &QmpMonitor{ + SBaseMonitor: *NewBaseMonitor(OnMonitorConnected, OnMonitorDisConnect, OnMonitorTimeout), + qmpEventFunc: qmpEventFunc, + commandQueue: make([]*Command, 0), + callbackQueue: make([]qmpMonitorCallBack, 0), + } + + // On qmp init must set capabilities + m.commandQueue = append(m.commandQueue, &Command{Execute: "qmp_capabilities"}) + m.callbackQueue = append(m.callbackQueue, nil) + + return m +} + +func (m *QmpMonitor) actionResult(res *Response) string { + if res.ErrorVal != nil { + log.Errorf("Qmp Monitor action result %s", res.ErrorVal.Error()) + return res.ErrorVal.Error() + } else { + return "" + } +} + +func (m *QmpMonitor) callBack(res *Response) { + m.mutex.Lock() + if len(m.callbackQueue) == 0 { + return + } + cb := m.callbackQueue[0] + m.callbackQueue = m.callbackQueue[1:] + m.mutex.Unlock() + if cb != nil { + go cb(res) + } +} + +func (m *QmpMonitor) read(r io.Reader) { + if !m.checkReading() { + return + } + scanner := bufio.NewScanner(r) + for scanner.Scan() { + var objmap map[string]*json.RawMessage + b := scanner.Bytes() + if err := json.Unmarshal(b, &objmap); err != nil { + log.Errorln("Error, ", err.Error()) + continue + } + if val, ok := objmap["error"]; ok { + var res = &Response{} + res.ErrorVal = &Error{} + json.Unmarshal(*val, res.ErrorVal) + if id, ok := objmap["id"]; ok { + res.Id = string(*id) + } + m.callBack(res) + } else if val, ok := objmap["return"]; ok { + var res = &Response{} + res.Return = *val + if id, ok := objmap["id"]; ok { + res.Id = string(*id) + } + m.callBack(res) + } else if val, ok := objmap["event"]; ok { + var event = &Event{ + Event: string(*val), + Data: make(map[string]interface{}, 0), + Timestamp: new(Timestamp), + } + if data, ok := objmap["data"]; ok { + json.Unmarshal(*data, &event.Data) + } + if timestamp, ok := objmap["timestamp"]; ok { + json.Unmarshal(*timestamp, event.Timestamp) + } + m.watchEvent(event) + } else if val, ok := objmap["QMP"]; ok { + // On qmp connected + json.Unmarshal(*val, &objmap) + if val, ok = objmap["version"]; ok { + var version Version + json.Unmarshal(*val, &version) + m.QemuVersion = version.String() + } + + // remove reader timeout + m.rwc.SetReadDeadline(time.Time{}) + m.connected = true + m.timeout = false + go m.query() + go m.OnMonitorConnected() + } + } + + log.Infof("Scan over ...") + err := scanner.Err() + if err != nil { + log.Infof("QMP Disconnected: %s", err) + } + if m.timeout { + m.OnMonitorTimeout(err) + } else if m.connected { + m.connected = false + m.OnMonitorDisConnect(err) + } + m.reading = false +} + +func (m *QmpMonitor) watchEvent(event *Event) { + log.Infof(event.String()) + if m.qmpEventFunc != nil { + go m.qmpEventFunc(event) + } +} + +func (m *QmpMonitor) write(cmd []byte) error { + log.Infof("QMP Write: %s", string(cmd)) + length, index := len(cmd), 0 + for index < length { + i, err := m.rwc.Write(cmd) + if err != nil { + return err + } + index += i + } + return nil +} + +func (m *QmpMonitor) query() { + if !m.checkWriting() { + return + } + for { + if len(m.commandQueue) == 0 { + break + } + + // pop cmd + m.mutex.Lock() + cmd := m.commandQueue[0] + m.commandQueue = m.commandQueue[1:] + + c, _ := json.Marshal(cmd) + err := m.write(c) + m.mutex.Unlock() + if err != nil { + log.Errorf("Write %s to monitor error: %s", c, err) + break + } + } + m.writing = false +} + +func (m *QmpMonitor) Query(cmd *Command, cb qmpMonitorCallBack) { + // push cmd + m.mutex.Lock() + m.commandQueue = append(m.commandQueue, cmd) + m.callbackQueue = append(m.callbackQueue, cb) + m.mutex.Unlock() + + if m.connected { + if !m.writing { + go m.query() + } + if !m.reading { + go m.read(m.rwc) + } + } + +} + +func (m *QmpMonitor) Connect(host string, port int) error { + err := m.SBaseMonitor.Connect(host, port) + if err != nil { + return err + } + go m.read(m.rwc) + return nil +} + +func (m *QmpMonitor) parseCmd(cmd string) string { + re := regexp.MustCompile(`\s+`) + parts := re.Split(strings.TrimSpace(cmd), -1) + if parts[0] == "info" && len(parts) > 1 { + return "query-" + parts[1] + } else { + return parts[0] + } +} + +func (m *QmpMonitor) SimpleCommand(cmd string, callback StringCallback) { + cmd = m.parseCmd(cmd) + var cb func(res *Response) + if callback != nil { + cb = func(res *Response) { + if res.ErrorVal != nil { + callback(res.ErrorVal.Error()) + } else { + callback(string(res.Return)) + } + } + } + c := &Command{Execute: cmd} + m.Query(c, cb) +} + +func (m *QmpMonitor) HumanMonitorCommand(cmd string, callback StringCallback) { + var ( + c = &Command{ + Execute: "human-monitor-command", + Args: map[string]string{"command-line": cmd}, + } + + cb = func(res *Response) { + if res.ErrorVal != nil { + callback(res.ErrorVal.Error()) + } else { + callback(string(res.Return)) + } + } + ) + m.Query(c, cb) +} + +func (m *QmpMonitor) QueryStatus(callback StringCallback) { + cmd := &Command{Execute: "query-status"} + m.Query(cmd, m.parseStatus(callback)) +} + +func (m *QmpMonitor) parseStatus(callback StringCallback) qmpMonitorCallBack { + return func(res *Response) { + if res.ErrorVal != nil { + callback("unknown") + return + } + var val map[string]interface{} + err := json.Unmarshal(res.Return, &val) + if err != nil { + callback("unknown") + return + } + if status, ok := val["status"]; !ok { + callback("unknown") + } else { + str, _ := status.(string) + callback(str) + } + } +} + +// If get version error, callback with empty string +func (m *QmpMonitor) GetVersion(callback StringCallback) { + cmd := &Command{Execute: "query-version"} + m.Query(cmd, m.parseVersion(callback)) +} + +func (m *QmpMonitor) parseVersion(callback StringCallback) qmpMonitorCallBack { + return func(res *Response) { + if res.ErrorVal != nil { + callback("") + return + } + var version Version + err := json.Unmarshal(res.Return, &version) + if err != nil { + callback("") + } else { + callback(version.String()) + } + } +} + +func (m *QmpMonitor) GetBlocks(callback func(*jsonutils.JSONArray)) { + var cb = func(res *Response) { + if res.ErrorVal != nil { + callback(nil) + } + jr, err := jsonutils.Parse(res.Return) + if err != nil { + log.Errorf("Get block error %s", err) + callback(nil) + } + jra, _ := jr.(*jsonutils.JSONArray) + callback(jra) + } + + cmd := &Command{Execute: "query-block"} + m.Query(cmd, cb) +} + +func (m *QmpMonitor) ChangeCdrom(dev string, path string, callback StringCallback) { + m.HumanMonitorCommand(fmt.Sprintf("change %s %s", dev, path), callback) + // var ( + // args = map[string]interface{}{ + // "arguments": map[string]interface{}{ + // "device": dev, + // "target": path, + // }, + // } + // cmd = &Command{ + // Execute: "change", + // Args: args, + // } + + // cb = func(res *Response) { + // callback(m.actionResult(res)) + // } + // ) + + // m.Query(cmd, cb) +} + +func (m *QmpMonitor) EjectCdrom(dev string, callback StringCallback) { + m.HumanMonitorCommand(fmt.Sprintf("eject -f %s", dev), callback) + // XXX: 同下 + // var ( + // args = map[string]interface{}{ + // "arguments": map[string]interface{}{ + // "device": dev, + // "force": true, + // }, + // } + // cmd = &Command{ + // Execute: "eject", + // Args: args, + // } + + // cb = func(res *Response) { + // callback(m.actionResult(res)) + // } + // ) + + // m.Query(cmd, cb) +} + +func (m *QmpMonitor) DriveDel(idstr string, callback StringCallback) { + m.HumanMonitorCommand(fmt.Sprintf("drive_del %s", idstr), callback) + // XXX: 同下 + // var ( + // args = map[string]interface{}{ + // "arguments": map[string]interface{}{ + // "device": idstr, + // }, + // } + // cmd = &Command{ + // Execute: "drive_del", + // Args: args, + // } + + // cb = func(res *Response) { + // callback(m.actionResult(res)) + // } + // ) + + // m.Query(cmd, cb) +} + +func (m *QmpMonitor) DeviceDel(idstr string, callback StringCallback) { + m.HumanMonitorCommand(fmt.Sprintf("device_del %s", idstr), callback) + // XXX: 同下 + // var ( + // args = map[string]interface{}{ + // "arguments": map[string]interface{}{ + // "device": idstr, + // }, + // } + // cmd = &Command{ + // Execute: "device_del", + // Args: args, + // } + + // cb = func(res *Response) { + // callback(m.actionResult(res)) + // } + // ) + + // m.Query(cmd, cb) +} + +func (m *QmpMonitor) DriveAdd(bus string, params map[string]string, callback StringCallback) { + var paramsKvs = []string{} + for k, v := range params { + paramsKvs = append(paramsKvs, fmt.Sprintf("%s=%s", k, v)) + } + cmd := fmt.Sprintf("drive_add %s %s", bus, strings.Join(paramsKvs, ",")) + m.HumanMonitorCommand(cmd, callback) + // XXX: 同下 + // var ( + // args = map[string]interface{}{ + // "arguments": map[string]interface{}{ + // "bus": bus, + // "params": params, + // }, + // } + // cmd = &Command{ + // Execute: "drive_add", + // Args: args, + // } + + // cb = func(res *Response) { + // callback(m.actionResult(res)) + // } + // ) + + // m.Query(cmd, cb) +} + +func (m *QmpMonitor) DeviceAdd(dev string, params map[string]interface{}, callback StringCallback) { + var paramsKvs = []string{} + for k, v := range params { + paramsKvs = append(paramsKvs, fmt.Sprintf("%s=%v", k, v)) + } + cmd := fmt.Sprintf("device_add %s,%s", dev, strings.Join(paramsKvs, ",")) + m.HumanMonitorCommand(cmd, callback) + + // XXX: 参数不对,之后再调,先用着hmp的参数 + // var ( + // args = map[string]interface{}{ + // "arguments": map[string]interface{}{ + // "driver": dev, + // "params": params, + // }, + // } + // cmd = &Command{ + // Execute: "device_add", + // Args: args, + // } + + // cb = func(res *Response) { + // callback(m.actionResult(res)) + // } + // ) + + // m.Query(cmd, cb) +} + +func (m *QmpMonitor) MigrateSetCapability(capability, state string, callback StringCallback) { + var ( + cb = func(res *Response) { + callback(m.actionResult(res)) + } + st = false + ) + if state == "on" { + st = true + } + + cmd := &Command{ + Execute: "query-migrate-capabilities", + Args: map[string]interface{}{ + "capabilities": []interface{}{ + map[string]interface{}{ + "capability": capability, + "state": st, + }, + }, + }, + } + + m.Query(cmd, cb) +} + +func (m *QmpMonitor) Migrate( + destStr string, copyIncremental, copyFull bool, callback StringCallback, +) { + var ( + cb = func(res *Response) { + callback(m.actionResult(res)) + } + cmd = &Command{ + Execute: "migrate", + Args: map[string]interface{}{ + "uri": destStr, + "blk": copyFull, + "inc": copyIncremental, + }, + } + ) + + m.Query(cmd, cb) +} + +func (m *QmpMonitor) GetMigrateStatus(callback StringCallback) { + var ( + cmd = &Command{Execute: "query-migrate"} + cb = func(res *Response) { + if res.ErrorVal != nil { + callback(res.ErrorVal.Error()) + } else { + ret, err := jsonutils.Parse(res.Return) + if err != nil { + log.Errorf("Parse qmp res error: %s", err) + callback("") + } else { + log.Infof("Query migrate status: %s", ret.String()) + status, _ := ret.GetString("status") + callback(status) + } + } + } + ) + m.Query(cmd, cb) +} + +func (m *QmpMonitor) GetBlockJobs(callback func(jobs int)) { + var cb = func(res *Response) { + if res.ErrorVal != nil { + log.Errorln(res.ErrorVal.Error()) + callback(-1) + } else { + ret, err := jsonutils.Parse(res.Return) + if err != nil { + log.Errorf("Parse qmp res error: %s", err) + callback(-1) + } else { + jobs, _ := ret.GetArray() + callback(len(jobs)) + } + } + } + m.Query(&Command{Execute: "query-block-jobs"}, cb) +} + +func (m *QmpMonitor) ReloadDiskBlkdev(device, path string, callback StringCallback) { + var ( + cb = func(res *Response) { + callback(m.actionResult(res)) + } + cmd = &Command{ + Execute: "reload-disk-snapshot-blkdev-sync", + Args: map[string]string{ + "device": device, + "snapshot-file": path, + "mode": "existing", + "format": "qcow2", + }, + } + ) + m.Query(cmd, cb) +} + +func (m *QmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode string, unmap bool) { + var ( + cb = func(res *Response) { + callback(m.actionResult(res)) + } + cmd = &Command{ + Execute: "drive-mirror", + Args: map[string]interface{}{ + "device": drive, + "target": target, + "mode": "existing", + "sync": syncMode, + "unmap": unmap, + }, + } + ) + m.Query(cmd, cb) +} + +func (m *QmpMonitor) BlockStream(drive string, callback StringCallback) { + var ( + speed = 30 // MB/s + cb = func(res *Response) { + callback(m.actionResult(res)) + } + cmd = &Command{ + Execute: "block-stream", + Args: map[string]interface{}{ + "device": drive, + "speed": speed, + }, + } + ) + m.Query(cmd, cb) +} + +func (m *QmpMonitor) SetVncPassword(proto, password string, callback StringCallback) { + if len(password) > 8 { + password = password[:8] + } + var ( + cb = func(res *Response) { + callback(m.actionResult(res)) + } + cmd = &Command{ + Execute: "set_password", + Args: map[string]interface{}{ + "protocol": proto, + "password": password, + }, + } + ) + m.Query(cmd, cb) +} + +func (m *QmpMonitor) StartNbdServer(port int, exportAllDevice, writable bool, callback StringCallback) { + var cmd = "nbd_server_start" + if exportAllDevice { + cmd += " -a" + } + if writable { + cmd += " -w" + } + cmd += fmt.Sprintf(" 0.0.0.0:%d", port) + m.HumanMonitorCommand(cmd, callback) +} + +func (m *QmpMonitor) ResizeDisk(driveName string, sizeMB int64, callback StringCallback) { + cmd := fmt.Sprintf("block_resize %s %d", driveName, sizeMB) + m.HumanMonitorCommand(cmd, callback) +} diff --git a/pkg/hostman/monitor/qmp_test.go b/pkg/hostman/monitor/qmp_test.go new file mode 100644 index 0000000000..7d1c08cc17 --- /dev/null +++ b/pkg/hostman/monitor/qmp_test.go @@ -0,0 +1,31 @@ +package monitor + +import ( + "testing" + "time" + + "yunion.io/x/log" +) + +func TestQmpMonitor_Connect(t *testing.T) { + onConnected := func() { log.Infof("Monitor Connected") } + onDisConnect := func(error) { log.Infof("Monitor DisConnect") } + onTimeout := func(error) { log.Infof("Monitor Timeout") } + m := NewQmpMonitor(onDisConnect, onTimeout, onConnected, nil) + var host = "127.0.0.1" + var port = 56101 + m.Connect(host, port) + rawCallBack := func(res *Response) { log.Infof("OnCallback %s", res) } + // cmd0 := &Command{Execute: "qmp_capabilities"} + // m.Query(cmd0, rawCallBack) + cmd1 := &Command{ + Execute: "human-monitor-command", + Args: map[string]string{"command-line": "info block"}, + } + m.Query(cmd1, rawCallBack) + + statusCallBack := func(res string) { log.Infof("OnStatusCallback %s", res) } + m.QueryStatus(statusCallBack) + m.Disconnect() + time.Sleep(3 * time.Second) +} diff --git a/pkg/hostman/options/doc.go b/pkg/hostman/options/doc.go new file mode 100644 index 0000000000..89835859e0 --- /dev/null +++ b/pkg/hostman/options/doc.go @@ -0,0 +1 @@ +package options // import "yunion.io/x/onecloud/pkg/hostman/options" diff --git a/pkg/hostman/options/options.go b/pkg/hostman/options/options.go new file mode 100644 index 0000000000..39fef19f08 --- /dev/null +++ b/pkg/hostman/options/options.go @@ -0,0 +1,93 @@ +package options + +import "yunion.io/x/onecloud/pkg/cloudcommon" + +type SHostOptions struct { + cloudcommon.CommonOptions + + HostType string `help:"Host server type, either hypervisor or kubelet" default:"hypervisor"` + ListenInterface string `help:"Master address of host server"` + BridgeDriver string `help:"Bridge driver, bridge or openvswitch" default: "openvswitch"` + Networks []string `help:"Network interface information"` + Rack string `help:"Rack of host (optional)"` + Slots string `help:"Slots of host (optional)"` + Hostname string `help:"Customized host name"` + + ServersPath string `help:"Path for virtual server configuration files"` + ImageCachePath string `help:"Path for storing image caches"` + ImageCacheLimit int `help:"Maximal storage space for image caching, in GB" default:"20"` + AgentTempPath string `help:"Path for ESXi agent"` + AgentTempLimit int `help:"Maximal storage space for ESXi agent, in GB" default:"10"` + + RecycleDiskfile bool `help:"Recycle instead of remove deleted disk file" default:"true"` + RecycleDiskfileKeepDays int `help:"How long recycled files kept, default 28 days" default:"28"` + + EnableTemplateBacking bool `help:"Use template as backing file"` + AutoMergeBackingTemplate bool `help:"Automatically stream merging backing file"` + AutoMergeDelaySeconds int `help:"Seconds to delay mergeing backing file after VM start, default 15 minutes" default:"900"` + EnableFallocateDisk bool `help:"Automatically allocate all spaces using fallocate"` + + EnableMonitor bool `help:"Enable monitor"` + ReportInterval int `help:"Report interval in seconds", default:"60"` + + EnableTcBwlimit bool `help:"Enable linux tc bandwidth limit"` + BwDownloadBandwidth int `help:"Default ingress bandwidth in mbit (0 disabled)" default:"10"` + + DnsServer string `help:"Address of host DNS server"` + DnsServerLegacy string `help:"Deprecated Address of host DNS server"` + + ChntpwPath string `help:"path to chntpw tool" default:"/usr/local/bin/chntpw.static"` + OvmfPath string `help:"Path to OVMF.fd" default:"/opt/cloud/contrib/OVMF.fd"` + LinuxDefaultRootUser bool `help:"Default account for linux system is root"` + + BlockIoScheduler string `help:"Block IO scheduler, deadline or cfq" default:"deadline"` + EnableKsm bool `help:"Enable Kernel Same Page Merging"` + HugepagesOption string `help:"Hugepages option: disable|native|transparent" default:"transparent"` + EnableQmpMonitor bool `help:"Enable qmp monitor" default:"true"` + + PrivatePrefixes []string `help:"IPv4 private prefixes"` + LocalImagePath []string `help:"Local image storage paths"` + SharedStorages []string `help:"Path of shared storages"` + + DefaultQemuVersion string `help:"Default qemu version" default:"2.9.1"` + + // XXX: Need configure + // dhcp_relay = ('10.168.222.236', 67) => dhcp_relay = ['10.168.222.236', '67'] + GoDhcpRelay []string `help:"DHCP relay upstream"` + DhcpLeaseTime int `default:"100663296" help:"DHCP lease time in seconds"` + DhcpRenewalTime int `default:"67108864" help:"DHCP renewal time in seconds"` + + TunnelPaddingBytes int64 `help:"Specify tunnel padding bytes" default:"0"` + + CheckSystemServices bool `help:"Check system services (ntpd, telegraf) on startup" default:"true"` + + DhcpServerPort int `help:"Host dhcp server bind port" default:"67"` + DiskIsSsd bool `default:"false"` + FetcherfsPath string `default:"/opt/yunion/fetchclient/bin/fetcherfs" help:"Fuse fetcherfs path"` + + DefaultImageSaveFormat string `default:"qcow2" help:"Default image save format, default is qcow2, canbe vmdk"` + + DefaultReadBpsPerCpu int `default:"163840000" help:"Default read bps per cpu for hard IO limit"` + DefaultReadIopsPerCpu int `default:"1250" help:"Default read iops per cpu for hard IO limit"` + DefaultWriteBpsPerCpu int `default:"54525952" help:"Default write bps per cpu for hard IO limit"` + DefaultWriteIopsPerCpu int `default:"416" help:"Default write iops per cpu for hard IO limit"` + SetVncPassword bool `default:"true" help:"Auto set vnc password after monitor connected"` + UseBootVga bool `default:"false" help:"Use boot VGA GPU for guest"` + + EnableCpuBinding bool `default:"true" help:"Enable cpu binding and rebalance"` + EnableOpenflowController bool `default:"false"` + K8sClusterCidr string `default:"10.43.0.0/16" help:"Kubernetes cluster IP range"` + + PingRegionInterval int `default:"60" help:"interval to ping region, deefault is 1 minute"` + ManageNtpConfiguration bool `default:"true"` + LogSystemdUnits []string `help:"Systemd units log collected by fluent-bit"` + BandwidthLimit int `default:"50" help:"Bandwidth upper bound when migrating disk image in MB/sec"` + + SnapshotDirSuffix string `help:"Snapshot dir name equal diskId concat snapshot dir suffix" default:"_snap"` + SnapshotRecycleDay int `default:"1" help:"Snapshot Recycle delete Duration day"` + + EnableTelegraf bool `default:"true" help:"enable send monitoring data to telegraf"` + WindowsDefaultAdminUser bool `default:"true" help:"Default account for Windows system is Administrator"` +} + +var HostOptions SHostOptions diff --git a/pkg/hostman/storageman/core.go b/pkg/hostman/storageman/core.go new file mode 100644 index 0000000000..70c337efbc --- /dev/null +++ b/pkg/hostman/storageman/core.go @@ -0,0 +1,324 @@ +package storageman + +import ( + "context" + "fmt" + "io/ioutil" + "path" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/util/timeutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/storagetypes" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +const MINIMAL_FREE_SPACE = 128 + +type SStorageManager struct { + host hostutils.IHost + + Storages []IStorage + AgentStorage IStorage + + LocalStorageImagecacheManager IImageCacheManger + AgentStorageImagecacheManager IImageCacheManger + + RbdStorageImagecacheManagers map[string]IImageCacheManger + NfsStorageImagecacheManagers map[string]IImageCacheManger +} + +func NewStorageManager(host hostutils.IHost) (*SStorageManager, error) { + var ( + ret = &SStorageManager{ + host: host, + Storages: make([]IStorage, 0), + } + allFull = true + ) + + for _, d := range options.HostOptions.LocalImagePath { + s := NewLocalStorage(ret, d) + if s.Accessible() { + ret.Storages = append(ret.Storages, s) + if allFull && s.GetFreeSizeMb() > MINIMAL_FREE_SPACE { + allFull = false + } + } + } + + for _, d := range options.HostOptions.SharedStorages { + s := ret.NewSharedStorageInstance(d, "") + if s != nil { + ret.Storages = append(ret.Storages, s) + allFull = false + } + } + + if allFull { + return nil, fmt.Errorf("Not enough storage space!") + } + + if err := ret.initLocalStorageImagecache(); err != nil { + return nil, fmt.Errorf("Init Local storage image cache failed: %s", err) + } + + return ret, nil +} + +func (s *SStorageManager) Remove(storage IStorage) { + for index, iS := range s.Storages { + if iS.GetId() == storage.GetId() { + s.Storages = append(s.Storages[:index], s.Storages[index+1:]...) + break + } + } +} + +func (s *SStorageManager) GetZone() string { + return s.host.GetZone() +} + +func (s *SStorageManager) GetHostId() string { + return s.host.GetHostId() +} + +func (s *SStorageManager) GetMediumType() string { + return s.host.GetMediumType() +} + +func (s *SStorageManager) getLeasedUsedLocalStorage(cacheDir string, limit int) (string, error) { + var ( + maxFree int + spath string + maxStorage IStorage + ) + + for _, storage := range s.Storages { + + if _, ok := storage.(*SLocalStorage); ok { + cachePath := path.Join(storage.GetPath(), cacheDir) + if fileutils2.Exists(cachePath) { + spath = cachePath + break + } + free := storage.GetFreeSizeMb() + if maxFree < free { + maxFree = free + maxStorage = storage + } + } + } + + if len(spath) == 0 { + if maxFree >= limit*1024 { + spath = path.Join(maxStorage.GetPath(), cacheDir) + } else { + return "", fmt.Errorf("No local storage has free space larger than %dGB", limit) + } + } + return spath, nil +} + +func (s *SStorageManager) initLocalStorageImagecache() error { + var ( + cacheDir = "image_cache" + cachePath = options.HostOptions.ImageCachePath + limit = options.HostOptions.ImageCacheLimit + ) + + if len(cachePath) == 0 { + var err error + cachePath, err = s.getLeasedUsedLocalStorage(cacheDir, limit) + if err != nil { + return err + } + } + if len(cachePath) > 0 { + s.LocalStorageImagecacheManager = NewLocalImageCacheManager(s, cachePath, limit, true, "") + return nil + } else { + return fmt.Errorf("Cannot allocate image cache storage") + } +} + +// func (s *SStorageManager) initAgentStorageImagecache() { +// s.AgentStorageImagecacheManager = NewAgentImageCacheManager(s) +// } + +// func (s *SStorageManager) initAgentStorage() error { +// var cacheDir = "agent_tmp" +// var spath = options.HostOptions.AgentTempPath +// var limit = options.HostOptions.AgentTempLimit +// if len(spath) == 0 { +// var err error +// spath, err = s.getLeasedUsedLocalStorage(cacheDir, limit) +// if err != nil { +// return err +// } +// } +// if len(spath) != nil { +// // TODO: NewAgentStorage +// s.AgentStorage = NewAgentStorage(s, spath) +// } else { +// return fmt.Errorf("Cannot allocate agent storage") +// } +// } + +// func (s *SStorageManager) AddNfsStorage(storagecacheId, cachePath string) { +// if len(cachePath) == 0 { +// return +// } +// if s.NfsStorageImagecacheManagers == nil { +// s.NfsStorageImagecacheManagers = make(map[string]IImageCacheManger, 0) +// } +// s.NfsStorageImagecacheManagers[storagecacheId] = NewLocalImageCacheManager(s, cachePath, +// options.HostOptions.ImageCacheLimit, true, storagecacheId) +// } + +func (s *SStorageManager) GetStorage(storageId string) IStorage { + for _, storage := range s.Storages { + if storage.GetId() == storageId { + return storage + } + } + return nil +} + +func (s *SStorageManager) GetStorageDisk(storageId, diskId string) IDisk { + if storage := s.GetStorage(storageId); storage != nil { + return storage.GetDiskById(diskId) + } + return nil +} + +func (s *SStorageManager) GetStorageByPath(sPath string) IStorage { + for _, storage := range s.Storages { + if storage.GetPath() == sPath { + return storage + } + } + return nil +} + +func (s *SStorageManager) GetDiskByPath(diskPath string) IDisk { + pos := strings.LastIndex(diskPath, "/") + sPath := diskPath[:pos] + diskId := diskPath[pos+1:] + pos = strings.LastIndex(diskId, ".") + if pos > 0 { + diskId = diskId[:pos] + } + storage := s.GetStorageByPath(sPath) + if storage != nil { + return storage.GetDiskById(diskId) + } + return nil +} + +func (s *SStorageManager) GetTotalCapacity() int { + var capa = 0 + for _, s := range s.Storages { + capa += s.GetCapacity() + } + return capa +} + +func (s *SStorageManager) GetStoragecacheById(scId string) IImageCacheManger { + if s.LocalStorageImagecacheManager.GetId() == scId { + return s.LocalStorageImagecacheManager + } + if sc, ok := s.NfsStorageImagecacheManagers[scId]; ok { + return sc + } + if sc, ok := s.RbdStorageImagecacheManagers[scId]; ok { + return sc + } + return nil +} + +func (s *SStorageManager) NewSharedStorageInstance(mountPoint, storageType string) IStorage { + if storageType == storagetypes.STORAGE_NFS { + // TODO + // return NewNFSStorage(s, mountPoint) + } else if storageType == storagetypes.STORAGE_RBD || + strings.HasPrefix(mountPoint, storagetypes.STORAGE_RBD) { + // TODO + // return NewRBDStorage(s, mountPoint) + } + return nil +} + +func (s *SStorageManager) InitSharedStorageImageCache(storageType, storagecacheId, imagecachePath string, storage IStorage) { + if storageType == storagetypes.STORAGE_NFS { + // TODO + // s.InitNfsStorageImagecache(storagecacheId, imagecachePath) + } else if storageType == storagetypes.STORAGE_RBD { + if s.GetStoragecacheById(storagecacheId) == nil { + // TODO + // s.AddRbdStorageImagecache(imagecachePath, rbdStorage, storagecacheId) + } + } + +} + +var storageManager *SStorageManager + +func GetManager() *SStorageManager { + return storageManager +} + +func Manager() *SStorageManager { + return storageManager +} + +func Init(host hostutils.IHost) error { + var err error + storageManager, err = NewStorageManager(host) + return err +} + +func Stop() { + // pass do nothing +} + +func cleanDailyFiles(storagePath, subDir string, keepDay int) { + recycleDir := path.Join(storagePath, subDir) + if !fileutils2.Exists(recycleDir) { + return + } + + // before mark should be deleted + markTime := timeutils.UtcNow().Add(time.Hour * 24 * -1 * time.Duration(keepDay)) + files, err := ioutil.ReadDir(recycleDir) + if err != nil { + log.Errorln(err) + return + } + + for _, file := range files { + date, err := timeutils.ParseTimeStr(file.Name()) + if err != nil { + log.Errorln(err) + continue + } + if date.Before(markTime) { + log.Infof("Real delete %s", file) + subDirPath := path.Join(recycleDir, file.Name()) + procutils.NewCommand("rm", "-rf", subDirPath) + } + } +} + +func CleanRecycleDiskfiles(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + for _, d := range options.HostOptions.LocalImagePath { + cleanDailyFiles(d, _RECYCLE_BIN_, options.HostOptions.RecycleDiskfileKeepDays) + cleanDailyFiles(d, _IMGSAVE_BACKUPS_, options.HostOptions.RecycleDiskfileKeepDays) + } +} diff --git a/pkg/hostman/storageman/diskbase.go b/pkg/hostman/storageman/diskbase.go new file mode 100644 index 0000000000..bc902741c0 --- /dev/null +++ b/pkg/hostman/storageman/diskbase.go @@ -0,0 +1,107 @@ +package storageman + +import ( + "context" + "fmt" + "path" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/hostman/guestfs" +) + +type IDisk interface { + GetType() string + GetId() string + Probe() error + GetPath() string + GetSnapshotDir() string + GetDiskDesc() jsonutils.JSONObject + GetDiskSetupScripts(idx int) string + + DeleteAllSnapshot() error + Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + Resize(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + ResetFromSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + CleanupSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + + PrepareMigrate(liveMigrate bool) (string, error) + CreateFromUrl(context.Context, string) error + CreateFromTemplate(context.Context, string, string, int64) (jsonutils.JSONObject, error) + CreateFromImageFuse(context.Context, string) error + CreateRaw(ctx context.Context, sizeMb int, diskFromat string, fsFormat string, + encryption bool, diskId string, back string) (jsonutils.JSONObject, error) + PostCreateFromImageFuse() + CreateSnapshot(snapshotId string) error + DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error + DeployGuestFs(diskPath string, guestDesc *jsonutils.JSONDict, + deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) +} + +type SBaseDisk struct { + Id string + Storage IStorage +} + +func NewBaseDisk(storage IStorage, id string) *SBaseDisk { + var ret = new(SBaseDisk) + ret.Storage = storage + ret.Id = id + return ret +} + +func (d *SBaseDisk) GetId() string { + return d.Id +} + +func (d *SBaseDisk) GetPath() string { + return path.Join(d.Storage.GetPath(), d.Id) +} + +func (d *SBaseDisk) Probe() error { + return fmt.Errorf("Not implemented") +} + +func (d *SBaseDisk) Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (d *SBaseDisk) CreateFromUrl(context.Context, string) error { + return fmt.Errorf("Not implemented") +} + +func (d *SBaseDisk) CreateFromTemplate(context.Context, string, string, int64) (jsonutils.JSONObject, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (d *SBaseDisk) Resize(context.Context, interface{}) (jsonutils.JSONObject, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (d *SBaseDisk) GetZone() string { + return d.Storage.GetZone() +} + +func (d *SBaseDisk) DeployGuestFs(diskPath string, guestDesc *jsonutils.JSONDict, + deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) { + var kvmDisk = NewKVMGuestDisk(diskPath) + if kvmDisk.Connect() { + defer kvmDisk.Disconnect() + log.Infof("Kvm Disk Connect Success !!") + + if root := kvmDisk.MountKvmRootfs(); root != nil { + defer kvmDisk.UmountKvmRootfs(root) + return guestfs.DeployGuestFs(root, guestDesc, deployInfo) + } else { + return nil, fmt.Errorf("Kvm Disk Mount error") + } + } else { + return nil, fmt.Errorf("Kvm disk connecterror") + } +} + +func (d *SBaseDisk) GetDiskSetupScripts(diskIndex int) string { + return "" +} diff --git a/pkg/hostman/storageman/disklocal.go b/pkg/hostman/storageman/disklocal.go new file mode 100644 index 0000000000..4fab39f418 --- /dev/null +++ b/pkg/hostman/storageman/disklocal.go @@ -0,0 +1,524 @@ +package storageman + +import ( + "context" + "fmt" + "os" + "path" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/appctx" + "yunion.io/x/onecloud/pkg/cloudcommon/storagetypes" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/fuseutils" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemuimg" + "yunion.io/x/pkg/utils" +) + +var _ALTER_SUFFIX_ = ".alter" + +type SLocalDisk struct { + SBaseDisk + isAlter bool +} + +func NewLocalDisk(storage IStorage, id string) *SLocalDisk { + var ret = new(SLocalDisk) + ret.SBaseDisk = *NewBaseDisk(storage, id) + return ret +} + +func (d *SBaseDisk) GetType() string { + return storagetypes.STORAGE_LOCAL +} + +func (d *SLocalDisk) getPath() string { + return path.Join(d.Storage.GetPath(), d.Id) +} + +func (d *SLocalDisk) getAlterPath() string { + return path.Join(d.Storage.GetPath(), d.Id, _ALTER_SUFFIX_) +} + +func (d *SLocalDisk) GetPath() string { + if d.isAlter { + return d.getAlterPath() + } else { + return d.getPath() + } +} + +func (d *SLocalDisk) GetSnapshotDir() string { + return path.Join(d.Storage.GetSnapshotDir(), d.Id+options.HostOptions.SnapshotDirSuffix) +} + +func (d *SLocalDisk) Probe() error { + if fileutils2.Exists(d.getPath()) { + d.isAlter = false + return nil + } else if fileutils2.Exists(d.getAlterPath()) { + d.isAlter = true + return nil + } + return fmt.Errorf("Disk not found") +} + +func (d *SLocalDisk) UmountFuseImage() { + mntPath := path.Join(d.Storage.GetFuseMountPath(), d.Id) + procutils.NewCommand("umount", mntPath).Run() + procutils.NewCommand("rm", "-rf", mntPath).Run() +} + +func (d *SLocalDisk) Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + dpath := d.GetPath() + log.Infof("Delete guest disk %s", dpath) + if err := d.Storage.DeleteDiskfile(dpath); err != nil { + return nil, err + } + d.UmountFuseImage() + + /* ???????????????? + files = os.listdir(self.storage.path) + for f in files: + if f.startswith(self.id): + if not re.match(r'[a-z0-9\-]*\.\d{14}', f): + path = os.path.join(self.storage.path, f) + print 'delete backing-file:', path + self.storage.delete_diskfile(path) + */ + + d.Storage.RemoveDisk(d) + return nil, nil +} + +func (d *SLocalDisk) Resize(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + diskInfo, ok := params.(*jsonutils.JSONDict) + if !ok { + return nil, hostutils.ParamsError + } + + sizeMb, _ := diskInfo.Int("size") + disk, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + log.Errorln(err) + return nil, err + } + if err := disk.Resize(int(sizeMb)); err != nil { + return nil, err + } + if options.HostOptions.EnableFallocateDisk { + // TODO + // d.Fallocate() + } + + if err = d.ResizeFs(); err != nil { + return nil, err + } + + return d.GetDiskDesc(), nil +} + +func (d *SLocalDisk) ResizeFs() error { + disk := NewKVMGuestDisk(d.GetPath()) + if disk.Connect() { + defer disk.Disconnect() + if err := disk.ResizePartition(); err != nil { + return err + } + } + return nil +} + +func (d *SLocalDisk) CreateFromImageFuse(ctx context.Context, url string) error { + log.Infof("Create from image fuse %s", url) + + var ( + localPath = d.Storage.GetFuseTmpPath() + mntPath = path.Join(d.Storage.GetFuseMountPath()) + contentPath = path.Join(mntPath, "content") + newImg, err = qemuimg.NewQemuImage(d.getPath()) + ) + + if err != nil { + log.Errorln(err) + return err + } + + if newImg.IsValid() && newImg.IsChained() && newImg.BackFilePath != contentPath { + if err := newImg.Delete(); err != nil { + log.Errorln(err) + return err + } + } + if !newImg.IsValid() || newImg.IsChained() { + if err := fuseutils.MountFusefs(options.HostOptions.FetcherfsPath, url, localPath, + auth.GetTokenString(), mntPath, fuseutils.DEFAULT_BLOCKSIZE); err != nil { + log.Errorln(err) + return err + } + } + if !newImg.IsValid() { + if err := newImg.CreateQcow2(0, false, contentPath); err != nil { + log.Errorln(err) + return err + } + } + + return nil +} + +func (d *SLocalDisk) CreateFromTemplate(ctx context.Context, imageId, format string, size int64) (jsonutils.JSONObject, error) { + ret, err := d.createFromTemplate(ctx, imageId, format) + if err != nil { + return nil, err + } + retSize, _ := ret.Int("disk_size") + log.Infof("REQSIZE: %d, RETSIZE: %d", size, retSize) + if size > retSize { + params := jsonutils.NewDict() + params.Set("size", jsonutils.NewInt(size)) + return d.Resize(ctx, params) + } + return ret, nil +} + +func (d *SLocalDisk) createFromTemplate(ctx context.Context, imageId, format string) (jsonutils.JSONObject, error) { + var imageCacheManager = storageManager.LocalStorageImagecacheManager + imageCache := imageCacheManager.AcquireImage(ctx, imageId, d.GetZone(), "", "") + if imageCache != nil { + defer imageCacheManager.ReleaseImage(imageId) + cacheImagePath := imageCache.GetPath() + + if fileutils2.Exists(d.GetPath()) { + err := os.Remove(d.GetPath()) + if err != nil { + log.Errorln(err) + return nil, fmt.Errorf("Fail to Create disk %s", d.Id) + } + } + + newImg, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + log.Errorln(err) + return nil, err + } + if err := newImg.CreateQcow2(0, false, cacheImagePath); err != nil { + log.Errorln(err) + return nil, fmt.Errorf("Fail to create disk %s", d.Id) + } + return d.GetDiskDesc(), nil + + } else { + return nil, fmt.Errorf("Fail to fetch image %s", imageId) + } +} + +func (d *SLocalDisk) CreateFromUrl(ctx context.Context, url string) error { + remoteFile := remotefile.NewRemoteFile(ctx, url, d.getPath(), false, "", -1, nil, "", "") + if remoteFile.Fetch() { + if options.HostOptions.EnableFallocateDisk { + //TODO + // d.fallocate() + } + return nil + } else { + return fmt.Errorf("Fail to fetch image from %s", url) + } +} + +func (d *SLocalDisk) CreateRaw(ctx context.Context, sizeMB int, diskFormat, fsFormat string, + encryption bool, uuid string, back string) (jsonutils.JSONObject, error) { + if fileutils2.Exists(d.GetPath()) { + os.Remove(d.GetPath()) + } + + img, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + log.Errorln(err) + return nil, err + } + + switch diskFormat { + case "qcow2": + err = img.CreateQcow2(sizeMB, false, back) + case "vmdk": + err = img.CreateVmdk(sizeMB, false) + default: + err = img.CreateRaw(sizeMB) + } + + if err != nil { + log.Errorln(err) + fmt.Errorf("create_raw: Fail to create disk") + } + + if options.HostOptions.EnableFallocateDisk { + // TODO + // d.Fallocate + } + + if utils.IsInStringArray(fsFormat, []string{"swap", "ext2", "ext3", "ext4", "xfs"}) { + d.FormatFs(fsFormat, uuid) + } + + return d.GetDiskDesc(), nil +} + +func (d *SLocalDisk) FormatFs(fsFormat, uuid string) { + log.Infof("Make disk %s fs %s", uuid, fsFormat) + gd := NewKVMGuestDisk(d.GetPath()) + if gd.Connect() { + defer gd.Disconnect() + if err := gd.MakePartition(fsFormat); err == nil { + err = gd.FormatPartition(fsFormat, uuid) + if err != nil { + log.Errorln(err) + } + } else { + log.Errorln(err) + } + } +} + +func (d *SLocalDisk) GetDiskDesc() jsonutils.JSONObject { + qemuImg, err := qemuimg.NewQemuImage(d.getPath()) + if err != nil { + log.Errorln(err) + return nil + } + + var desc = jsonutils.NewDict() + desc.Set("disk_id", jsonutils.NewString(d.Id)) + desc.Set("disk_size", jsonutils.NewInt(qemuImg.SizeBytes/1024/1024)) + desc.Set("format", jsonutils.NewString(qemuImg.Format.String())) + desc.Set("disk_path", jsonutils.NewString(d.getPath())) + return desc +} + +func (d *SLocalDisk) GetDiskSetupScripts(diskIndex int) string { + cmd := "" + cmd += fmt.Sprintf("DISK_%d=%s\n", diskIndex, d.getPath()) + cmd += fmt.Sprintf("if [ ! -f $DISK_%d ]; then\n", diskIndex) + cmd += fmt.Sprintf(" DISK_%d=$DISK_%d%s\n", diskIndex, diskIndex, _ALTER_SUFFIX_) + cmd += "fi\n" + return cmd +} + +func (d *SLocalDisk) PostCreateFromImageFuse() { + mntPath := path.Join(d.Storage.GetFuseMountPath(), d.Id) + if _, err := procutils.NewCommand("umount", mntPath).Run(); err != nil { + log.Errorln(err) + } + if _, err := procutils.NewCommand("rm", "-rf", mntPath).Run(); err != nil { + log.Errorln(err) + } +} + +func (d *SLocalDisk) CreateSnapshot(snapshotId string) error { + snapshotDir := d.GetSnapshotDir() + if !fileutils2.Exists(snapshotDir) { + _, err := procutils.NewCommand("mkdir", "-p", snapshotDir).Run() + if err != nil { + log.Errorln(err) + return err + } + } + snapshotPath := path.Join(snapshotDir, snapshotId) + _, err := procutils.NewCommand("mv", "-f", d.getPath(), snapshotPath).Run() + if err != nil { + log.Errorln(err) + return err + } + img, err := qemuimg.NewQemuImage(d.getPath()) + if err != nil { + log.Errorln(err) + procutils.NewCommand("mv", "-f", snapshotPath, d.getPath()).Run() + return err + } + if err := img.CreateQcow2(0, false, snapshotPath); err != nil { + log.Errorf("Snapshot create image error %s", err) + procutils.NewCommand("mv", "-f", snapshotPath, d.getPath()).Run() + return err + } + return nil +} + +func (d *SLocalDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error { + snapshotDir := d.GetSnapshotDir() + if len(convertSnapshot) > 0 { + if !fileutils2.Exists(snapshotDir) { + _, err := procutils.NewCommand("mkdir", "-p", snapshotDir).Run() + if err != nil { + log.Errorln(err) + return err + } + } + convertSnapshotPath := path.Join(snapshotDir, convertSnapshot) + output := convertSnapshotPath + ".tmp" + if fileutils2.Exists(output) { + procutils.NewCommand("rm", "-f", output).Run() + } + img, err := qemuimg.NewQemuImage(convertSnapshotPath) + if err != nil { + log.Errorln(err) + return err + } + if err = img.Convert2Qcow2To(output, true); err != nil { + log.Errorln(err) + procutils.NewCommand("rm", "-f", output).Run() + return err + } + if _, err = procutils.NewCommand("rm", "-f", convertSnapshotPath).Run(); err != nil { + log.Errorln(err) + return err + } + if _, err = procutils.NewCommand("mv", "-f", output, convertSnapshotPath).Run(); err != nil { + log.Errorln(err) + return err + } + if !pendingDelete { + _, err = procutils.NewCommand("rm", "-f", path.Join(snapshotDir, snapshotId)).Run() + if err != nil { + log.Errorln(err) + return err + } + } + return nil + } else { + _, err := procutils.NewCommand("rm", "-f", path.Join(snapshotDir, snapshotId)).Run() + if err != nil { + log.Errorln(err) + return err + } + return nil + } +} + +func (d *SLocalDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + // diskInfo, ok := params.(*jsonutils.JSONDict) + // if !ok { + // return nil, hostutils.ParamsError + // } + if err := d.Probe(); err != nil { + return nil, err + } + destDir := d.Storage.GetImgsaveBackupPath() + if _, err := procutils.NewCommand("mkdir", "-p", destDir).Run(); err != nil { + log.Errorln(err) + return nil, err + } + backupPath := path.Join(destDir, fmt.Sprintf("%s.%s", d.Id, appctx.AppContextTaskId(ctx))) + if _, err := procutils.NewCommand("cp", "--sparse=always", "-f", d.GetPath(), backupPath).Run(); err != nil { + log.Errorln(err) + procutils.NewCommand("rm", "-f", backupPath).Run() + return nil, err + } + res := jsonutils.NewDict() + res.Set("backup", jsonutils.NewString(backupPath)) + return res, nil +} + +func (d *SLocalDisk) ResetFromSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + resetParams, ok := params.(*SDiskReset) + if !ok { + return nil, hostutils.ParamsError + } + + snapshotDir := d.GetSnapshotDir() + snapshotPath := path.Join(snapshotDir, resetParams.SnapshotId) + diskTmpPath := d.GetPath() + "_reset.tmp" + if _, err := procutils.NewCommand("mv", "-f", d.GetPath(), diskTmpPath).Run(); err != nil { + log.Errorln(err) + return nil, err + } + if !resetParams.OutOfChain { + img, err := qemuimg.NewQemuImage(d.GetPath()) + if err != nil { + log.Errorln(err) + procutils.NewCommand("mv", "-f", diskTmpPath, d.GetPath()).Run() + return nil, err + } + if err := img.CreateQcow2(0, false, snapshotPath); err != nil { + log.Errorln(err) + procutils.NewCommand("mv", "-f", diskTmpPath, d.GetPath()).Run() + return nil, err + } + } else { + if _, err := procutils.NewCommand("cp", "-f", snapshotPath, d.GetPath()).Run(); err != nil { + log.Errorln(err) + procutils.NewCommand("mv", "-f", diskTmpPath, d.GetPath()).Run() + return nil, err + } + } + _, err := procutils.NewCommand("rm", "-f", diskTmpPath).Run() + return nil, err +} + +func (d *SLocalDisk) CleanupSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + cleanupParams, ok := params.(*SDiskCleanupSnapshots) + if !ok { + return nil, hostutils.ParamsError + } + snapshotDir := d.GetSnapshotDir() + for _, snapshotId := range cleanupParams.ConvertSnapshots { + snapId, _ := snapshotId.GetString() + snapshotPath := path.Join(snapshotDir, snapId) + output := snapshotPath + "_convert.tmp" + img, err := qemuimg.NewQemuImage(snapshotPath) + if err != nil { + log.Errorln(err) + return nil, err + } + if err = img.Convert2Qcow2To(output, true); err != nil { + log.Errorln(err) + return nil, err + } + if procutils.NewCommand("mv", "-f", output, snapshotPath).Run(); err != nil { + procutils.NewCommand("rm", "-f", output).Run() + log.Errorln(err) + return nil, err + } + } + + for _, snapshotId := range cleanupParams.DeleteSnapshots { + snapId, _ := snapshotId.GetString() + if _, err := procutils.NewCommand("rm", "-f", path.Join(snapshotDir, snapId)).Run(); err != nil { + log.Errorln(err) + return nil, err + } + } + return nil, nil +} + +func (d *SLocalDisk) DeleteAllSnapshot() error { + snapshotDir := d.GetSnapshotDir() + log.Infof("Delete disk(%s) snapshot dir %s", d.Id, snapshotDir) + _, err := procutils.NewCommand("rm", "-rf", snapshotDir).Run() + return err +} + +func (d *SLocalDisk) PrepareMigrate(liveMigrate bool) (string, error) { + disk, err := qemuimg.NewQemuImage(d.getPath()) + if err != nil { + log.Errorln(err) + return "", err + } + ret, err := disk.WholeChainFormatIs("qcow2") + if err != nil { + log.Errorln(err) + return "", err + } + if liveMigrate && !ret { + return "", fmt.Errorf("Disk format doesn't support live migrate") + } + if disk.IsChained() { + return disk.BackFilePath, nil + } + return "", nil +} diff --git a/pkg/hostman/storageman/diskutils.go b/pkg/hostman/storageman/diskutils.go new file mode 100644 index 0000000000..49f4bedc14 --- /dev/null +++ b/pkg/hostman/storageman/diskutils.go @@ -0,0 +1,167 @@ +package storageman + +import ( + "fmt" + "io/ioutil" + "path" + "path/filepath" + "strings" + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/hostman/storageman/nbd" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemutils" +) + +const MAX_TRIES = 3 + +type SKVMGuestDisk struct { + imagePath string + nbdDev string + partitions []*guestfs.SKVMGuestDiskPartition +} + +func NewKVMGuestDisk(imagePath string) *SKVMGuestDisk { + var ret = new(SKVMGuestDisk) + ret.imagePath = imagePath + ret.partitions = make([]*guestfs.SKVMGuestDiskPartition, 0) + return ret +} + +func (d *SKVMGuestDisk) Connect() bool { + d.nbdDev = nbd.GetNBDManager().AcquireNbddev() + if len(d.nbdDev) == 0 { + log.Errorln("Cannot get nbd device") + return false + } + + var cmd []string + if strings.HasPrefix(d.imagePath, "rbd:") || d.getImageFormat() == "raw" { + cmd = []string{qemutils.GetQemuNbd(), "-c", d.nbdDev, "-f", "raw", d.imagePath} + } else { + cmd = []string{qemutils.GetQemuNbd(), "-c", d.nbdDev, d.imagePath} + } + _, err := procutils.NewCommand(cmd[0], cmd[1:]...).Run() + if err != nil { + log.Errorln(err.Error()) + return false + } + + var tried uint = 0 + for len(d.partitions) == 0 && tried < MAX_TRIES { + time.Sleep((1 << tried) * time.Second) + err = d.findPartitions() + if err != nil { + log.Errorln(err.Error()) + return false + } + tried += 1 + } + d.setupLVMS() + return true +} + +func (d *SKVMGuestDisk) getImageFormat() string { + lines, err := procutils.NewCommand(qemutils.GetQemuImg(), "info", d.imagePath).Run() + if err != nil { + return "" + } + imgStr := strings.Split(string(lines), "\n") + for i := 0; i < len(imgStr); i++ { + if strings.HasPrefix(imgStr[i], "file format: ") { + return imgStr[i][len("file format: "):] + } + } + return "" +} + +func (d *SKVMGuestDisk) findPartitions() error { + if len(d.nbdDev) == 0 { + return fmt.Errorf("Want find partitions but dosen't have nbd dev") + } + dev := filepath.Base(d.nbdDev) + devpath := filepath.Dir(d.nbdDev) + files, err := ioutil.ReadDir(devpath) + if err != nil { + return err + } + for i := 0; i < len(files); i++ { + if files[i].Name() != dev && strings.HasPrefix(files[i].Name(), dev+"p") { + var part = guestfs.NewKVMGuestDiskPartition(path.Join(devpath, files[i].Name())) + d.partitions = append(d.partitions, part) + } + } + + // XXX: HACK reverse partitions + for i, j := 0, len(d.partitions)-1; i < j; i, j = i+1, j-1 { + d.partitions[i], d.partitions[j] = d.partitions[j], d.partitions[i] + } + return nil +} + +func (d *SKVMGuestDisk) setupLVMS() error { + //TODO?? 可能不需要开发这里 + return fmt.Errorf("not implement right now") +} + +func (d *SKVMGuestDisk) Disconnect() bool { + if len(d.nbdDev) > 0 { + // TODO?? PutdownLVMS ?? + _, err := procutils.NewCommand(qemutils.GetQemuNbd(), "-d", d.nbdDev).Run() + if err != nil { + log.Errorln(err.Error()) + return false + } + nbd.GetNBDManager().ReleaseNbddev(d.nbdDev) + d.nbdDev = "" + d.partitions = d.partitions[len(d.partitions):] + return true + } else { + return false + } +} + +func (d *SKVMGuestDisk) MountKvmRootfs() fsdriver.IRootFsDriver { + for i := 0; i < len(d.partitions); i++ { + if d.partitions[i].Mount() { + if fs := guestfs.DetectRootFs(d.partitions[i]); fs != nil { + log.Infof("Use rootfs %s", fs) + return fs + } else { + d.partitions[i].Umount() + } + } + } + return nil +} + +func (d *SKVMGuestDisk) UmountKvmRootfs(fd fsdriver.IRootFsDriver) { + if part := fd.GetPartition(); part != nil { + part.Umount() + } +} + +func (d *SKVMGuestDisk) MakePartition(fs string) error { + return fileutils2.Mkpartition(d.nbdDev, fs) +} + +func (d *SKVMGuestDisk) FormatPartition(fs, uuid string) error { + return fileutils2.FormatPartition(fmt.Sprintf("%sp1", d.nbdDev), fs, uuid) +} + +func (d *SKVMGuestDisk) ResizePartition() error { + return fileutils2.ResizeDiskFs(d.nbdDev, 0) +} + +func (d *SKVMGuestDisk) Zerofree() { + startTime := time.Now() + for _, part := range d.partitions { + part.Zerofree() + } + log.Infof("Zerofree takes %f seconds", time.Now().Sub(startTime).Seconds()) +} diff --git a/pkg/hostman/storageman/doc.go b/pkg/hostman/storageman/doc.go new file mode 100644 index 0000000000..43cd93d266 --- /dev/null +++ b/pkg/hostman/storageman/doc.go @@ -0,0 +1 @@ +package storageman // import "yunion.io/x/onecloud/pkg/hostman/storageman" diff --git a/pkg/hostman/storageman/imagecachebase.go b/pkg/hostman/storageman/imagecachebase.go new file mode 100644 index 0000000000..a1800981cd --- /dev/null +++ b/pkg/hostman/storageman/imagecachebase.go @@ -0,0 +1,277 @@ +package storageman + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "sync" + "syscall" + "time" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/qemuimg" +) + +const ( + _TMP_SUFFIX_ = ".tmp" + _INF_SUFFIX_ = ".inf" + + CHECK_TIMEOUT = 3600 * time.Second +) + +type IImageCache interface { + GetPath() string + Load() bool + Acquire(ctx context.Context, zone, srcUrl, format string) bool + Release() + Remove(ctx context.Context) error + GetImageId() string + + GetDesc() *remotefile.SImageDesc +} + +type SLocalImageCache struct { + imageId string + Manager IImageCacheManger + Size int64 + Desc *remotefile.SImageDesc + + consumerCount int + cond *sync.Cond + lastCheckTime time.Time + + remoteFile *remotefile.SRemoteFile +} + +func NewLocalImageCache(imageId string, imagecacheManager IImageCacheManger) *SLocalImageCache { + imageCache := new(SLocalImageCache) + imageCache.imageId = imageId + imageCache.Manager = imagecacheManager + imageCache.cond = sync.NewCond(new(sync.Mutex)) + return imageCache +} + +func (l *SLocalImageCache) GetDesc() *remotefile.SImageDesc { + return l.Desc +} + +func (l *SLocalImageCache) GetImageId() string { + return l.imageId +} + +func (l *SLocalImageCache) Load() bool { + var ( + imgPath = l.GetPath() + infPath = l.GetInfPath() + desc = &remotefile.SImageDesc{} + ) + if fileutils2.Exists(imgPath) { + if !fileutils2.Exists(infPath) { + img, err := qemuimg.NewQemuImage(imgPath) + if err != nil { + log.Errorln(err) + return false + } + if !img.IsValid() { + return false + } + chksum, err := fileutils2.MD5(imgPath) + if err != nil { + log.Errorln(err) + return false + } + desc = &remotefile.SImageDesc{ + Format: string(img.Format), + Id: l.imageId, + Chksum: chksum, + Path: imgPath, + Size: l.GetSize(), + } + bdesc, err := json.Marshal(desc) + if err != nil { + log.Errorln(err) + return false + } + err = fileutils2.FilePutContents(infPath, string(bdesc), false) + if err != nil { + log.Errorf("File put content error %s", err) + return false + } + } else { + sdesc, err := fileutils2.FileGetContents(infPath) + if err != nil { + log.Errorf("File get contents error %s", err) + return false + } + err = json.Unmarshal([]byte(sdesc), desc) + if err != nil { + log.Errorf("Unmarshal desc %s error %s", sdesc, err) + return false + } + } + if len(desc.Chksum) > 0 && len(desc.Id) > 0 && desc.Id == l.imageId { + l.Desc = desc + return true + } + } + + tmpPath := l.GetTmpPath() + if fileutils2.Exists(tmpPath) { + syscall.Unlink(tmpPath) + } + return false +} + +func (l *SLocalImageCache) needCheck() bool { + if time.Now().Sub(l.lastCheckTime) > CHECK_TIMEOUT { + return true + } + return false +} + +func (l *SLocalImageCache) Release() { + l.cond.L.Lock() + defer l.cond.L.Unlock() + + l.consumerCount -= 1 +} + +func (l *SLocalImageCache) Acquire(ctx context.Context, zone, srcUrl, format string) bool { + ret, exit := l.prepare(ctx, zone, srcUrl, format) + if exit { + return ret + } + return l.fetch(ctx, zone, srcUrl, format) +} + +func (l *SLocalImageCache) prepare(ctx context.Context, zone, srcUrl, format string) (bool, bool) { + l.cond.L.Lock() + defer l.cond.L.Unlock() + + for l.remoteFile != nil { + l.cond.Wait() + } + + if l.remoteFile == nil && l.Desc != nil && (l.consumerCount > 0 || !l.needCheck()) { + l.consumerCount++ + return true, true + } + url, err := auth.GetServiceURL("image", "", zone, "internal") + if err != nil { + log.Errorf("Failed to acquire image %s", err) + return false, true + } + url += fmt.Sprintf("/images/%s", l.imageId) + if len(format) == 0 { + format = "qcow2" + } + url += fmt.Sprintf("?format=%s", format) + + l.remoteFile = remotefile.NewRemoteFile(ctx, url, + l.GetPath(), false, "", -1, nil, l.GetTmpPath(), srcUrl) + return false, false +} + +func (l *SLocalImageCache) fetch(ctx context.Context, zone, srcUrl, format string) bool { + if (fileutils2.Exists(l.GetPath()) && + l.remoteFile.VerifyIntegrity()) || l.remoteFile.Fetch() { + if len(l.Manager.GetId()) > 0 { + _, err := hostutils.RemoteStoragecacheCacheImage(ctx, + l.Manager.GetId(), l.imageId, "ready", l.GetPath()) + if err != nil { + log.Errorf("Fail to update host cached image: %s", err) + } + } + l.cond.L.Lock() + defer l.cond.L.Unlock() + + l.Desc = l.remoteFile.GetInfo() + l.Size = l.GetSize() / 1024 / 1024 + l.Desc.Id = l.imageId + l.remoteFile = nil + l.lastCheckTime = time.Now() + l.consumerCount++ + l.cond.Broadcast() + + bDesc, err := json.Marshal(l.Desc) + if err != nil { + log.Errorln("Marshal image desc error %s", err) + return false + } + + err = fileutils2.FilePutContents(l.GetInfPath(), string(bDesc), false) + if err != nil { + log.Errorln("File put content error %s", err) + return false + } + return true + } else { + l.cond.L.Lock() + defer l.cond.L.Unlock() + l.Desc = nil + l.remoteFile = nil + l.cond.Broadcast() + return false + } +} + +func (l *SLocalImageCache) Remove(ctx context.Context) error { + if fileutils2.Exists(l.GetPath()) { + if err := syscall.Unlink(l.GetPath()); err != nil { + return err + } + } + if fileutils2.Exists(l.GetInfPath()) { + if err := syscall.Unlink(l.GetInfPath()); err != nil { + return err + } + } + if fileutils2.Exists(l.GetTmpPath()) { + if err := syscall.Unlink(l.GetTmpPath()); err != nil { + return err + } + } + + go func() { + _, err := modules.Storagecachedimages.Detach(hostutils.GetComputeSession(ctx), + l.Manager.GetId(), l.imageId, nil) + if err != nil { + log.Errorf("Fail to delete host cached image: %s", err) + } + }() + + return nil +} + +func (l *SLocalImageCache) GetPath() string { + return path.Join(l.Manager.GetPath(), l.imageId) +} + +func (l *SLocalImageCache) GetTmpPath() string { + return l.GetPath() + _TMP_SUFFIX_ +} + +func (l *SLocalImageCache) GetInfPath() string { + return l.GetPath() + _INF_SUFFIX_ +} + +func (l *SLocalImageCache) GetSize() int64 { + if fi, err := os.Stat(l.GetPath()); err != nil { + log.Errorln(err) + return 0 + } else { + return fi.Size() + } +} + +type SRbdImageCache struct { + imageId string + Manager IImageCacheManger +} diff --git a/pkg/hostman/storageman/imagecachemanager.go b/pkg/hostman/storageman/imagecachemanager.go new file mode 100644 index 0000000000..26a39a973e --- /dev/null +++ b/pkg/hostman/storageman/imagecachemanager.go @@ -0,0 +1,203 @@ +package storageman + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "sync" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/pkg/util/regutils" +) + +type IImageCacheManger interface { + GetId() string + GetPath() string + SetStoragecacheId(string) + + // for diskhandler + PrefetchImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) + DeleteImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) + + AcquireImage(ctx context.Context, imageId, zone, srcUrl, format string) IImageCache + ReleaseImage(imageId string) + LoadImageCache(imageId string) +} + +type SBaseImageCacheManager struct { + storagemanager *SStorageManager + storagecacaheId string + cachePath string + cachedImages map[string]IImageCache + mutex *sync.Mutex +} + +func (c *SBaseImageCacheManager) GetPath() string { + return c.cachePath +} + +func (c *SBaseImageCacheManager) GetId() string { + return c.storagecacaheId +} + +func (c *SBaseImageCacheManager) SetStoragecacheId(scid string) { + c.storagecacaheId = scid +} + +type SLocalImageCacheManager struct { + SBaseImageCacheManager + limit int + isTemplate bool +} + +func NewLocalImageCacheManager(manager *SStorageManager, cachePath string, limit int, isTemplete bool, storagecacheId string) *SLocalImageCacheManager { + imageCacheManager := new(SLocalImageCacheManager) + imageCacheManager.storagemanager = manager + imageCacheManager.storagecacaheId = storagecacheId + imageCacheManager.cachePath = cachePath + imageCacheManager.limit = limit + imageCacheManager.isTemplate = isTemplete + imageCacheManager.cachedImages = make(map[string]IImageCache, 0) + imageCacheManager.mutex = new(sync.Mutex) + if !fileutils2.Exists(cachePath) { + procutils.NewCommand("mkdir", "-p", cachePath).Run() + } + imageCacheManager.loadCache() + return imageCacheManager +} + +func (c *SLocalImageCacheManager) loadCache() { + if len(c.cachePath) == 0 { + return + } + c.mutex.Lock() + defer c.mutex.Unlock() + files, _ := ioutil.ReadDir(c.cachePath) + for _, f := range files { + if regutils.MatchUUIDExact(f.Name()) { + c.LoadImageCache(f.Name()) + } + } +} + +func (c *SLocalImageCacheManager) LoadImageCache(imageId string) { + imageCache := NewLocalImageCache(imageId, c) + if imageCache.Load() { + c.cachedImages[imageId] = imageCache + } +} + +func (c *SLocalImageCacheManager) AcquireImage(ctx context.Context, imageId, zone, srcUrl, format string) IImageCache { + c.mutex.Lock() + defer c.mutex.Unlock() + + img, ok := c.cachedImages[imageId] + if !ok { + img = NewLocalImageCache(imageId, c) + c.cachedImages[imageId] = img + } + if img.Acquire(ctx, zone, srcUrl, format) { + return img + } else { + return nil + } +} + +func (c *SLocalImageCacheManager) ReleaseImage(imageId string) { + c.mutex.Lock() + defer c.mutex.Unlock() + if img, ok := c.cachedImages[imageId]; ok { + img.Release() + } +} + +func (c *SLocalImageCacheManager) DeleteImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) { + body, ok := data.(*jsonutils.JSONDict) + if !ok { + return nil, hostutils.ParamsError + } + + imageId, _ := body.GetString("image_id") + return nil, c.removeImage(ctx, imageId) +} + +func (c *SLocalImageCacheManager) removeImage(ctx context.Context, imageId string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if img, ok := c.cachedImages[imageId]; ok { + delete(c.cachedImages, imageId) + return img.Remove(ctx) + } + return nil +} + +func (c *SLocalImageCacheManager) PrefetchImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) { + body, ok := data.(*jsonutils.JSONDict) + if !ok { + return nil, hostutils.ParamsError + } + + imageId, err := body.GetString("image_id") + if err != nil { + return nil, err + } + format, _ := body.GetString("format") + srcUrl, _ := body.GetString("src_url") + + if imgCache := c.AcquireImage(ctx, imageId, storageManager.GetZone(), + srcUrl, format); imgCache != nil { + defer imgCache.Release() + + res := jsonutils.NewDict() + res.Set("image_id", jsonutils.NewString(imageId)) + res.Set("path", jsonutils.NewString(imgCache.GetPath())) + + var ( + name string + size int64 + ) + if desc := imgCache.GetDesc(); desc != nil { + name = desc.Name + size = desc.Size + } + if size == 0 { + if fi, err := os.Stat(imgCache.GetPath()); err != nil { + size = fi.Size() + } + } + if len(name) == 0 { + name = imageId + } + + res.Set("name", jsonutils.NewString(name)) + res.Set("size", jsonutils.NewInt(size)) + return res, nil + } else { + return nil, fmt.Errorf("Failed to fetch image %s", imageId) + } +} + +// TODO: AgentImageCacheManager +type SAgentImageCacheManager struct { + storagemanager *SStorageManager +} + +func NewAgentImageCacheManager(storagemanager *SStorageManager) *SAgentImageCacheManager { + return &SAgentImageCacheManager{storagemanager} +} + +type SRbdImageCacheManager struct { + SBaseImageCacheManager + pool, prefix string + storage IStorage +} + +func NewRbdImageCacheManager() *SRbdImageCacheManager { + // TODO + return nil +} diff --git a/pkg/hostman/storageman/nbd/doc.go b/pkg/hostman/storageman/nbd/doc.go new file mode 100644 index 0000000000..f6fd8ea3c8 --- /dev/null +++ b/pkg/hostman/storageman/nbd/doc.go @@ -0,0 +1 @@ +package nbd // import "yunion.io/x/onecloud/pkg/hostman/storageman/nbd" diff --git a/pkg/hostman/storageman/nbd/nbdman.go b/pkg/hostman/storageman/nbd/nbdman.go new file mode 100644 index 0000000000..b0ad71d166 --- /dev/null +++ b/pkg/hostman/storageman/nbd/nbdman.go @@ -0,0 +1,69 @@ +package nbd + +import ( + "fmt" + "sync" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/fileutils2" +) + +type SNBDManager struct { + nbdDevs map[string]bool + nbdLock *sync.Mutex +} + +var nbdManager *SNBDManager + +func init() { + nbdManager = NewNBDManager() +} + +func GetNBDManager() *SNBDManager { + return nbdManager +} + +func NewNBDManager() *SNBDManager { + var ret = new(SNBDManager) + ret.nbdDevs = make(map[string]bool, 0) + ret.nbdLock = new(sync.Mutex) + ret.findNbdDevices() + return ret +} + +func (m *SNBDManager) findNbdDevices() { + var i = 0 + for { + if fileutils2.Exists(fmt.Sprintf("/dev/nbd%d", i)) { + m.nbdDevs[fmt.Sprintf("/dev/nbd%d", i)] = false + i++ + } else { + break + } + } + log.Infof("NBD_DEVS: %#v", m.nbdDevs) +} + +func (m *SNBDManager) AcquireNbddev() string { + defer m.nbdLock.Unlock() + m.nbdLock.Lock() + for nbdDev := range m.nbdDevs { + if fileutils2.IsBlockDeviceUsed(nbdDev) { + m.nbdDevs[nbdDev] = true + } + if !m.nbdDevs[nbdDev] { + m.nbdDevs[nbdDev] = true + return nbdDev + } + } + return "" +} + +func (m *SNBDManager) ReleaseNbddev(nbddev string) { + if _, ok := m.nbdDevs[nbddev]; ok { + defer m.nbdLock.Unlock() + m.nbdLock.Lock() + m.nbdDevs[nbddev] = false + } +} diff --git a/pkg/hostman/storageman/nbd/nbdman_test.go b/pkg/hostman/storageman/nbd/nbdman_test.go new file mode 100644 index 0000000000..7176c91b2d --- /dev/null +++ b/pkg/hostman/storageman/nbd/nbdman_test.go @@ -0,0 +1,10 @@ +package nbd + +import ( + "testing" +) + +func TestGetNBDManager(t *testing.T) { + nbdman := GetNBDManager() + t.Logf("Acquire nbd: %s, %v", nbdman.AcquireNbddev(), nbdman.nbdDevs) +} diff --git a/pkg/hostman/storageman/remotefile/doc.go b/pkg/hostman/storageman/remotefile/doc.go new file mode 100644 index 0000000000..714545ed7c --- /dev/null +++ b/pkg/hostman/storageman/remotefile/doc.go @@ -0,0 +1 @@ +package remotefile // import "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" diff --git a/pkg/hostman/storageman/remotefile/remotefile.go b/pkg/hostman/storageman/remotefile/remotefile.go new file mode 100644 index 0000000000..ee4e2bf20a --- /dev/null +++ b/pkg/hostman/storageman/remotefile/remotefile.go @@ -0,0 +1,227 @@ +package remotefile + +import ( + "compress/zlib" + "context" + "io" + "net/http" + "os" + "syscall" + "time" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +type SImageDesc struct { + Name string `json:"name"` + Format string `json:"format"` + Id string `json:"id:` + Chksum string `json:"chksum"` + Path string `json:"path"` + Size int64 `json:"size"` +} + +type SRemoteFile struct { + ctx context.Context + url string + downloadUrl string + localPath string + tmpPath string + preChksum string + compress bool + timeout time.Duration + extraHeaders map[string]string + + chksum string + format string + name string +} + +func NewRemoteFile( + ctx context.Context, url, localPath string, compress bool, + PreChksum string, timeout int, extraHeaders map[string]string, + tmpPath string, downloadUrl string, +) *SRemoteFile { + if timeout <= 0 { + timeout = 24 * 3600 //24 hours + } + if len(tmpPath) == 0 { + tmpPath = localPath + } + + return &SRemoteFile{ + ctx: ctx, + url: url, + localPath: localPath, + compress: compress, + preChksum: PreChksum, + timeout: time.Duration(timeout) * time.Second, + extraHeaders: extraHeaders, + tmpPath: tmpPath, + downloadUrl: downloadUrl, + } +} + +func (r *SRemoteFile) Fetch() bool { + if len(r.preChksum) > 0 { + return r.fetch(r.preChksum) + } else if fileutils2.Exists(r.localPath) { + if !r.VerifyIntegrity() { + return r.fetch("") + } else { + return true + } + } else { + return r.fetch("") + } +} + +func (r *SRemoteFile) GetInfo() *SImageDesc { + fi, err := os.Stat(r.localPath) + if err != nil { + log.Errorln(err) + return nil + } + + return &SImageDesc{ + Name: r.name, + Format: r.format, + Chksum: r.chksum, + Path: r.localPath, + Size: fi.Size(), + } +} + +func (r *SRemoteFile) VerifyIntegrity() bool { + if r.download(false, "") { + localChksum, err := fileutils2.MD5(r.localPath) + if err != nil { + log.Errorln(err) + return false + } + if localChksum == r.chksum { + log.Infof("identical chksum, skip download") + return true + } + } + return r.fetch("") +} + +func (r *SRemoteFile) fetch(preChksum string) bool { + var ( + fetchSucc = false + retryCnt = 0 + ) + + for !fetchSucc && retryCnt < 3 { + r.format = "" + r.chksum = "" + fetchSucc = r.download(true, preChksum) + if fetchSucc { + if len(r.chksum) > 0 && fileutils2.Exists(r.tmpPath) { + if localChksum, err := fileutils2.MD5(r.tmpPath); err != nil { + log.Errorln(err) + fetchSucc = false + } else if r.chksum != localChksum { + fetchSucc = false + } + } + + if !fetchSucc { + retryCnt += 1 + } else if r.localPath != r.tmpPath { + if fileutils2.Exists(r.localPath) { + if err := syscall.Unlink(r.localPath); err != nil { + log.Errorln(err) + } + } + if err := syscall.Rename(r.tmpPath, r.localPath); err != nil { + log.Errorln(err) + } + } + } + } + return fetchSucc +} + +func (r *SRemoteFile) download(getData bool, preChksum string) bool { + fi, err := os.Create(r.tmpPath) + if err != nil { + log.Errorln(err) + return false + } + defer fi.Close() + + var header = http.Header{} + header.Set("X-Auth-Token", auth.GetTokenString()) + if len(preChksum) > 0 { + header.Set("X-Image-Meta-Checksum", preChksum) + } + if r.compress { + header.Set("X-Compress-Content", "zlib") + } + if len(r.extraHeaders) > 0 { + for k, v := range r.extraHeaders { + header.Set(k, v) + } + } + var method, url = "HEAD", r.url + if getData { + if len(r.downloadUrl) > 0 { + url = r.downloadUrl + } + method = "GET" + } + + httpCli := httputils.GetTimeoutClient(r.timeout) + resp, err := httputils.Request(httpCli, r.ctx, + httputils.THttpMethod(method), url, header, nil, false) + if err != nil { + log.Errorln(err) + return false + } else { + if resp.StatusCode < 300 { + defer resp.Body.Close() + if getData { + var reader = resp.Body + + if r.compress { + zlibRC, err := zlib.NewReader(resp.Body) + if err != nil { + log.Errorf("New zlib Reader error: %s", err) + return false + } + defer zlibRC.Close() + reader = zlibRC + } + + _, err := io.Copy(fi, reader) + if err != nil { + log.Errorln(err) + return false + } + } + r.setProperties(resp.Header) + return true + } else if resp.StatusCode == 304 { + if err := os.Remove(r.tmpPath); err != nil { + log.Errorf("Fail to remove file %s", r.tmpPath) + } + return true + } else { + log.Errorf("Remote file fetch error %d", resp.StatusCode) + return false + } + } + return false + +} + +func (r *SRemoteFile) setProperties(header http.Header) { + r.chksum = header.Get("X-Image-Meta-Checksum") + r.format = header.Get("X-Image-Meta-Disk_format") + r.name = header.Get("X-Image-Meta-Name") +} diff --git a/pkg/hostman/storageman/storagebase.go b/pkg/hostman/storageman/storagebase.go new file mode 100644 index 0000000000..d96f601ac2 --- /dev/null +++ b/pkg/hostman/storageman/storagebase.go @@ -0,0 +1,234 @@ +package storageman + +import ( + "context" + "fmt" + "sync" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/hostman/hostutils" +) + +var ( + _RECYCLE_BIN_ = "recycle_bin" + _IMGSAVE_BACKUPS_ = "imgsave_backups" +) + +type IStorage interface { + GetId() string + GetZone() string + + SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject) + SyncStorageInfo() (jsonutils.JSONObject, error) + StorageType() string + + SetPath(string) + GetPath() string + + GetSnapshotDir() string + GetSnapshotPathByIds(diskId, snapshotId string) string + DeleteSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + + GetFreeSizeMb() int + GetCapacity() int + + // Find owner disks first, if not found, call create disk + GetDiskById(diskId string) IDisk + CreateDisk(diskId string) IDisk + RemoveDisk(IDisk) + + // DeleteDisk(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) + + // *SDiskCreateByDiskinfo + CreateDiskByDiskinfo(context.Context, interface{}) (jsonutils.JSONObject, error) + SaveToGlance(context.Context, interface{}) (jsonutils.JSONObject, error) + + CreateSnapshotFormUrl(ctx context.Context, snapshotUrl, diskId, snapshotPath string) error + + DeleteDiskfile(diskPath string) error + GetFuseTmpPath() string + GetFuseMountPath() string + GetImgsaveBackupPath() string +} + +type SBaseStorage struct { + Manager *SStorageManager + StorageId string + Path string + StorageName string + StorageConf *jsonutils.JSONDict + StoragecacheId string + + Disks []IDisk + DiskLock *sync.Mutex +} + +func NewBaseStorage(manager *SStorageManager, path string) *SBaseStorage { + var ret = new(SBaseStorage) + ret.Disks = make([]IDisk, 0) + ret.DiskLock = new(sync.Mutex) + ret.Manager = manager + ret.Path = path + return ret +} + +func (s *SBaseStorage) GetId() string { + return s.StorageId +} + +func (s *SBaseStorage) GetPath() string { + return s.Path +} + +func (s *SBaseStorage) SetPath(p string) { + s.Path = p +} + +func (s *SBaseStorage) GetZone() string { + return s.Manager.GetZone() +} + +func (s *SBaseStorage) GetCapacity() int { + return s.GetAvailSizeMb() +} + +func (s *SBaseStorage) GetAvailSizeMb() int { + return s.GetTotalSizeMb() +} + +func (s *SBaseStorage) GetMediumType() string { + return s.Manager.GetMediumType() +} + +func (s *SBaseStorage) GetFreeSizeMb() int { + var stat syscall.Statfs_t + err := syscall.Statfs(s.Path, &stat) + if err != nil { + log.Errorln(err) + return -1 + } + return int(stat.Bavail * uint64(stat.Bsize) / 1024 / 1024) +} + +func (s *SBaseStorage) GetTotalSizeMb() int { + var stat syscall.Statfs_t + err := syscall.Statfs(s.Path, &stat) + if err != nil { + log.Errorln(err) + return -1 + } + return int(stat.Blocks * uint64(stat.Bsize) / 1024 / 1024) +} + +func (s *SBaseStorage) SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject) { + s.StorageId = storageId + s.StorageName = storageName + if dconf, ok := conf.(*jsonutils.JSONDict); ok { + s.StorageConf = dconf + } +} + +func (s *SBaseStorage) RemoveDisk(d IDisk) { + s.DiskLock.Lock() + defer s.DiskLock.Unlock() + + for i := 0; i < len(s.Disks); i++ { + if s.Disks[i].GetId() == d.GetId() { + s.Disks = append(s.Disks[:i], s.Disks[i+1:]...) + break + } + } +} + +func (s *SBaseStorage) DeleteDiskfile(diskpath string) error { + return fmt.Errorf("Not Implement") +} + +func (s *SBaseStorage) CreateDiskByDiskinfo(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + createParams, ok := params.(*SDiskCreateByDiskinfo) + if !ok { + return nil, hostutils.ParamsError + } + + if createParams.Disk != nil { + if !jsonutils.QueryBoolean(createParams.DiskInfo, "rebuild", false) { + return nil, fmt.Errorf("Disk exist") + } + if _, err := createParams.Disk.Delete(ctx, params); err != nil { + return nil, err + } + } + + disk := createParams.Storage.CreateDisk(createParams.DiskId) + if disk == nil { + return nil, fmt.Errorf("Fail to Create disk %s", createParams.DiskId) + } + + switch { + case createParams.DiskInfo.Contains("snapshot"): + log.Infof("CreateDiskFromSnpashot %s", createParams) + return s.CreateDiskFromSnpashot(ctx, disk, createParams) + case createParams.DiskInfo.Contains("image_id"): + log.Infof("CreateDiskFromTemplate %s", createParams) + return s.CreateDiskFromTemplate(ctx, disk, createParams) + case createParams.DiskInfo.Contains("size"): + log.Infof("CreateRawDisk %s", createParams) + return s.CreateRawDisk(ctx, disk, createParams) + default: + return nil, fmt.Errorf("Not fount") + } +} + +func (s *SBaseStorage) CreateRawDisk(ctx context.Context, disk IDisk, createParams *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) { + size, _ := createParams.DiskInfo.Int("size") + diskFromat, _ := createParams.DiskInfo.GetString("format") + fsFormat, _ := createParams.DiskInfo.GetString("fs_format") + encryption := jsonutils.QueryBoolean(createParams.DiskInfo, "encryption", false) + + return disk.CreateRaw(ctx, int(size), diskFromat, fsFormat, encryption, createParams.DiskId, "") +} + +func (s *SBaseStorage) CreateDiskFromTemplate(ctx context.Context, disk IDisk, createParams *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) { + var ( + imageId, _ = createParams.DiskInfo.GetString("image_id") + format = "qcow2" // force qcow2 + size, _ = createParams.DiskInfo.Int("size") + ) + + return disk.CreateFromTemplate(ctx, imageId, format, size) +} + +func (s *SBaseStorage) CreateDiskFromSnpashot(ctx context.Context, disk IDisk, createParams *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) { + var ( + // diskPath = path.Join(s.Path, createParams.DiskId) + snapshotUrl, _ = createParams.DiskInfo.GetString("snapshot_url") + transferProtocol, _ = createParams.DiskInfo.GetString("url") + ) + + if len(snapshotUrl) == 0 || len(transferProtocol) == 0 { + return nil, fmt.Errorf("Create disk form snapshot missing params snapshot url or protocol") + } + + if transferProtocol == "url" { + // TODO + // snapshotOutOfChain := jsonutils.QueryBoolean( + // createParams.DiskInfo, "snapshot_out_of_chain", false) + // // you wen ti... + // if err := s.CreateDiskFromUrl(ctx, snapshotUrl, diskPath, !snapshotOutOfChain); err != nil { + // return nil, err + // } + } else if transferProtocol == "fuse" { + if err := disk.CreateFromImageFuse(ctx, snapshotUrl); err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("Unkown protocol %s", transferProtocol) + } + return disk.GetDiskDesc(), nil +} + +func (s *SLocalStorage) GetImgsaveBackupPath() string { + return s.getSubdirPath(_IMGSAVE_BACKUPS_) +} diff --git a/pkg/hostman/storageman/storagehandler.go b/pkg/hostman/storageman/storagehandler.go new file mode 100644 index 0000000000..f85abd956f --- /dev/null +++ b/pkg/hostman/storageman/storagehandler.go @@ -0,0 +1,138 @@ +package storageman + +import ( + "context" + "fmt" + "net/http" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + storageKeyWords = []string{"storages"} + storageActionFuncs = map[string]storageActionFunc{ + "attach": storageAttach, + "detach": storageDetach, + "update": storageUpdate, + } +) + +type storageActionFunc func(context.Context, jsonutils.JSONObject) (interface{}, error) + +func AddStorageHandler(prefix string, app *appsrv.Application) { + for _, keyWords := range storageKeyWords { + app.AddHandler("POST", + fmt.Sprintf("%s/%s/", prefix, keyWords), + auth.Authenticate(storageActions)) + app.AddHandler("POST", + fmt.Sprintf("%s/%s//delete-snapshots", prefix, keyWords), + auth.Authenticate(storageDeleteSnapshots)) + } +} + +func storageActions(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + var action = params[""] + + if f, ok := storageActionFuncs[action]; !ok { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Not found")) + } else { + res, err := f(ctx, body) + if err != nil { + hostutils.Response(ctx, w, err) + } else if res != nil { + hostutils.Response(ctx, w, res) + } else { + hostutils.ResponseOk(ctx, w) + } + } +} + +func storageAttach(ctx context.Context, body jsonutils.JSONObject) (interface{}, error) { + mountPoint, err := body.GetString("mount_point") + if err != nil { + return nil, httperrors.NewMissingParameterError("mount_point") + } + + storageType, _ := body.GetString("storage_type") + storage := storageManager.NewSharedStorageInstance(mountPoint, storageType) + if storage == nil { + return nil, httperrors.NewBadRequestError("'Not Support Storage[%s] mount_point: %s", storageType, mountPoint) + } + + storagecacheId, _ := body.GetString("storagecache_id") + imagecachePath, _ := body.GetString("imagecache_path") + storageManager.InitSharedStorageImageCache(storageType, storagecacheId, imagecachePath, storage) + + storageId, _ := body.GetString("id") + storageName, _ := body.GetString("name") + storageConf, _ := body.Get("storage_conf") + storage.SetStorageInfo(storageId, storageName, storageConf) + return nil, nil +} + +func storageDetach(ctx context.Context, body jsonutils.JSONObject) (interface{}, error) { + mountPoint, err := body.GetString("mount_point") + if err != nil { + return nil, httperrors.NewMissingParameterError("mount_point") + } + storage := storageManager.GetStorageByPath(mountPoint) + + name, _ := body.GetString("name") + if storage == nil { + return nil, httperrors.NewBadRequestError("ShareStorage[%s] Has detach from host ...", name) + } + storageManager.Remove(storage) + return nil, nil +} + +func storageUpdate(ctx context.Context, body jsonutils.JSONObject) (interface{}, error) { + storageId, err := body.GetString("storage_id") + if err != nil { + return nil, httperrors.NewMissingParameterError("storage_id") + } + storageConf, err := body.Get("storage_conf") + if err != nil { + return nil, httperrors.NewMissingParameterError("storage_conf") + } + storage := storageManager.GetStorage(storageId) + params := jsonutils.NewDict() + params.Set("details", jsonutils.JSONTrue) + ret, err := modules.Hoststorages.Get(hostutils.GetComputeSession(context.Background()), + storageManager.GetHostId(), storageId, params) + if err != nil { + log.Errorln(err) + return nil, err + } + if ret == nil || storage == nil { + return nil, httperrors.NewNotFoundError("Storage %s not found", storageId) + } + storageName, _ := ret.GetString("storage") + storage.SetStorageInfo(storageId, storageName, storageConf) + mountPoint, _ := ret.GetString("mount_point") + storage.SetPath(mountPoint) + return nil, nil +} + +func storageDeleteSnapshots(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + var storageId = params[""] + storage := storageManager.GetStorage(storageId) + if storage == nil { + hostutils.Response(ctx, w, httperrors.NewNotFoundError("Stroage Not found")) + return + } + diskId, err := body.GetString("disk_id") + if err != nil { + hostutils.Response(ctx, w, httperrors.NewImageNotFoundError("disk_id")) + return + } + hostutils.DelayTask(ctx, storage.DeleteSnapshots, diskId) + hostutils.ResponseOk(ctx, w) +} diff --git a/pkg/hostman/storageman/storagehelper.go b/pkg/hostman/storageman/storagehelper.go new file mode 100644 index 0000000000..7ebc464c01 --- /dev/null +++ b/pkg/hostman/storageman/storagehelper.go @@ -0,0 +1,29 @@ +package storageman + +import ( + "fmt" + + "yunion.io/x/jsonutils" +) + +type SDiskCreateByDiskinfo struct { + DiskId string + Disk IDisk + DiskInfo jsonutils.JSONObject + + Storage IStorage +} + +func (i *SDiskCreateByDiskinfo) String() string { + return fmt.Sprintf("disk_id: %s, disk_info: %s", i.DiskId, i.DiskInfo) +} + +type SDiskReset struct { + SnapshotId string + OutOfChain bool +} + +type SDiskCleanupSnapshots struct { + ConvertSnapshots []jsonutils.JSONObject + DeleteSnapshots []jsonutils.JSONObject +} diff --git a/pkg/hostman/storageman/storagelocal.go b/pkg/hostman/storageman/storagelocal.go new file mode 100644 index 0000000000..90832d7f7d --- /dev/null +++ b/pkg/hostman/storageman/storagelocal.go @@ -0,0 +1,456 @@ +package storageman + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path" + "regexp" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/timeutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/cronman" + "yunion.io/x/onecloud/pkg/cloudcommon/storagetypes" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemuimg" +) + +var ( + _FUSE_MOUNT_PATH_ = "fusemnt" + _FUSE_TMP_PATH_ = "fusetmp" + _SNAPSHOT_PATH_ = "snapshots" + DELETEING_SNAPSHOTS = map[string]bool{} +) + +type SLocalStorage struct { + SBaseStorage +} + +func NewLocalStorage(manager *SStorageManager, path string) *SLocalStorage { + var ret = new(SLocalStorage) + ret.SBaseStorage = *NewBaseStorage(manager, path) + ret.StartSnapshotRecycle() + return ret +} + +func (s *SLocalStorage) GetFuseTmpPath() string { + return path.Join(s.Path, _FUSE_TMP_PATH_) +} + +func (s *SLocalStorage) GetFuseMountPath() string { + return path.Join(s.Path, _FUSE_MOUNT_PATH_) +} + +func (s *SLocalStorage) StorageType() string { + return storagetypes.STORAGE_LOCAL +} + +func (s *SLocalStorage) GetSnapshotDir() string { + return path.Join(s.Path, _SNAPSHOT_PATH_) +} + +func (s *SLocalStorage) GetSnapshotPathByIds(diskId, snapshotId string) string { + return path.Join(s.GetSnapshotDir(), diskId+options.HostOptions.SnapshotDirSuffix, snapshotId) +} + +func (s *SLocalStorage) SyncStorageInfo() (jsonutils.JSONObject, error) { + content := jsonutils.NewDict() + content.Set("name", jsonutils.NewString(s.StorageName)) + content.Set("capacity", jsonutils.NewInt(int64(s.GetAvailSizeMb()))) + content.Set("storage_type", jsonutils.NewString(s.StorageType())) + content.Set("medium_type", jsonutils.NewString(s.GetMediumType())) + content.Set("zone", jsonutils.NewString(s.GetZone())) + if len(s.Manager.LocalStorageImagecacheManager.GetId()) > 0 { + content.Set("storagecache_id", + jsonutils.NewString(s.Manager.LocalStorageImagecacheManager.GetId())) + } + var ( + err error + res jsonutils.JSONObject + ) + + log.Infof("Sync storage info %s", s.StorageId) + + if len(s.StorageId) > 0 { + res, err = modules.Storages.Put( + hostutils.GetComputeSession(context.Background()), + s.StorageId, content) + } else { + res, err = modules.Storages.Create( + hostutils.GetComputeSession(context.Background()), content) + } + if err != nil { + log.Errorf("SyncStorageInfo Failed: %s: %s", content, err) + } + return res, err +} + +func (s *SLocalStorage) GetDiskById(diskId string) IDisk { + s.DiskLock.Lock() + defer s.DiskLock.Unlock() + for i := 0; i < len(s.Disks); i++ { + if s.Disks[i].GetId() == diskId { + if s.Disks[i].Probe() == nil { + return s.Disks[i] + } + } + } + var disk = NewLocalDisk(s, diskId) + if disk.Probe() == nil { + s.Disks = append(s.Disks, disk) + return disk + } else { + return nil + } +} + +func (s *SLocalStorage) CreateDisk(diskId string) IDisk { + s.DiskLock.Lock() + defer s.DiskLock.Unlock() + disk := NewLocalDisk(s, diskId) + s.Disks = append(s.Disks, disk) + return disk +} + +func (s *SLocalStorage) Accessible() bool { + if !fileutils2.Exists(s.Path) { + if _, err := procutils.NewCommand("mkdir", "-p", s.Path).Run(); err != nil { + log.Errorln(err) + } + } + if fileutils2.IsDir(s.Path) && fileutils2.Writable(s.Path) { + return true + } else { + return false + } +} + +func (s *SLocalStorage) DeleteDiskfile(diskpath string) error { + if options.HostOptions.RecycleDiskfile { + var ( + destDir = s.getRecyclePath() + destFile = fmt.Sprintf("%s.%d", path.Base(diskpath), time.Now().Unix()) + ) + if _, err := procutils.NewCommand("mkdir", "-p", destDir).Run(); err != nil { + return err + } + _, err := procutils.NewCommand("mv", "-f", diskpath, path.Join(destDir, destFile)).Run() + return err + } else { + _, err := procutils.NewCommand("rm", "-rf", diskpath).Run() + return err + } +} + +func (s *SLocalStorage) getRecyclePath() string { + return s.getSubdirPath(_RECYCLE_BIN_) +} + +func (s *SLocalStorage) getSubdirPath(subdir string) string { + spath := path.Join(s.Path, subdir) + today := timeutils.CompactTime(time.Now()) + return path.Join(spath, today) +} + +func (s *SLocalStorage) SaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + data, ok := params.(*jsonutils.JSONDict) + if !ok { + return nil, hostutils.ParamsError + } + + var ( + imageId, _ = data.GetString("image_id") + imagePath, _ = data.GetString("image_path") + compress = jsonutils.QueryBoolean(data, "compress", true) + format, _ = data.GetString("format") + ) + + if err := s.saveToGlance(ctx, imageId, imagePath, compress, format); err != nil { + log.Errorf("Save to glance failed: %s", err) + s.onSaveToGlanceFailed(ctx, imageId) + } + + imagecacheManager := s.Manager.LocalStorageImagecacheManager + if len(imagecacheManager.GetId()) > 0 { + _, err := procutils.NewCommand("rm", "-f", imagePath).Run() + return nil, err + } else { + dstPath := path.Join(imagecacheManager.GetPath(), imageId) + if _, err := procutils.NewCommand("mv", imagePath, dstPath).Run(); err != nil { + log.Errorf("Fail to move saved image to cache: %s", err) + } + imagecacheManager.LoadImageCache(imageId) + _, err := hostutils.RemoteStoragecacheCacheImage(ctx, + imagecacheManager.GetId(), imageId, "ready", dstPath) + if err != nil { + log.Errorf("Fail to remote cache image: %s", err) + } + } + return nil, nil +} + +func (s *SLocalStorage) saveToGlance(ctx context.Context, imageId, imagePath string, + compress bool, format string) error { + var ( + kvmDisk = NewKVMGuestDisk(imagePath) + osInfo string + relInfo *fsdriver.SReleaseInfo + ) + + if err := func() error { + if kvmDisk.Connect() { + defer kvmDisk.Disconnect() + + if root := kvmDisk.MountKvmRootfs(); root != nil { + defer kvmDisk.UmountKvmRootfs(root) + + osInfo = root.GetOs() + relInfo = root.GetReleaseInfo(root.GetPartition()) + if compress { + if err := root.PrepareFsForTemplate(root.GetPartition()); err != nil { + log.Errorln(err) + return err + } + } + } + + if compress { + kvmDisk.Zerofree() + } + } + return nil + }(); err != nil { + return err + } + + if compress { + origin, err := qemuimg.NewQemuImage(imagePath) + if err != nil { + log.Errorln(err) + return err + } + if len(format) == 0 { + format = options.HostOptions.DefaultImageSaveFormat + } + if format == "qcow2" { + if err := origin.Convert2Qcow2(true); err != nil { + log.Errorln(err) + return err + } + } else { + if err := origin.Convert2Vmdk(true); err != nil { + log.Errorln(err) + return err + } + } + } + + f, err := os.Open(imagePath) + if err != nil { + return err + } + defer f.Close() + finfo, err := f.Stat() + if err != nil { + return err + } + size := finfo.Size() + + var params = jsonutils.NewDict() + if len(osInfo) > 0 { + params.Set("os_type", jsonutils.NewString(osInfo)) + } + if relInfo != nil { + params.Set("os_distribution", jsonutils.NewString(relInfo.Distro)) + if len(relInfo.Version) > 0 { + params.Set("os_version", jsonutils.NewString(relInfo.Version)) + } + if len(relInfo.Arch) > 0 { + params.Set("os_arch", jsonutils.NewString(relInfo.Arch)) + } + if len(relInfo.Version) > 0 { + params.Set("os_language", jsonutils.NewString(relInfo.Language)) + } + } + params.Set("image_id", jsonutils.NewString(imageId)) + + _, err = modules.Images.Upload(hostutils.GetImageSession(ctx, s.GetZone()), + params, f, size) + // TODO + // notify_template_ready + return err +} + +func (s *SLocalStorage) onSaveToGlanceFailed(ctx context.Context, imageId string) { + params := jsonutils.NewDict() + params.Set("status", jsonutils.NewString("killed")) + _, err := modules.Images.Update(hostutils.GetImageSession(ctx, s.GetZone()), + imageId, params) + if err != nil { + log.Errorln(err) + } +} + +func (s *SLocalStorage) CreateSnapshotFormUrl( + ctx context.Context, snapshotUrl, diskId, snapshotPath string, +) error { + remoteFile := remotefile.NewRemoteFile(ctx, snapshotUrl, snapshotPath, + false, "", -1, nil, "", "") + if remoteFile.Fetch() { + return nil + } else { + return fmt.Errorf("Fail to fetch snapshot from %s", snapshotUrl) + } +} + +func (s *SLocalStorage) DeleteSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + diskId, ok := params.(string) + if !ok { + return nil, hostutils.ParamsError + } + snapshotDir := path.Join(s.GetSnapshotDir(), diskId+options.HostOptions.SnapshotDirSuffix) + output, err := procutils.NewCommand("rm", "-rf", snapshotDir).Run() + if err != nil { + return nil, fmt.Errorf("Delete snapshot dir failed: %s", output) + } + return nil, nil +} + +/*************************Background delete snapshot job****************************/ + +func (s *SLocalStorage) StartSnapshotRecycle() { + log.Infof("Snapshot recyle job started") + if !fileutils2.Exists(s.GetSnapshotDir()) { + procutils.NewCommand("mkdir", "-p", s.GetSnapshotDir()).Run() + } + cronman.GetCronJobManager(false).AddJob2("SnapshotRecycle", options.HostOptions.SnapshotRecycleDay, 2, 0, 0, s.snapshotRecycle, true) +} + +func (s *SLocalStorage) snapshotRecycle(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + res, err := modules.Snapshots.GetById(hostutils.GetComputeSession(ctx), "max-count", nil) + if err != nil { + log.Errorln(err) + return + } + maxSnapshotCount, err := res.Int("max_count") + if err != nil { + log.Errorln("Request region get snapshot max count failed") + return + } + files, err := ioutil.ReadDir(s.GetSnapshotDir()) + if err != nil { + log.Errorln(err) + return + } + for _, file := range files { + s.checkSnapshots(file.Name(), int(maxSnapshotCount)) + } +} + +func (s *SLocalStorage) checkSnapshots(snapshotDir string, maxSnapshotCount int) { + re := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}_snap$`) + if !re.MatchString(snapshotDir) { + log.Warningf("snapshot_dir got unexcept file %s", snapshotDir) + return + } + + diskId := snapshotDir[:len(snapshotDir)-len(options.HostOptions.SnapshotDirSuffix)] + snapshotPath := path.Join(s.GetSnapshotDir(), snapshotDir) + + // If disk is Deleted, request delete this disk all snapshots + if !fileutils2.Exists(path.Join(s.Path, diskId)) && fileutils2.Exists(snapshotPath) { + params := jsonutils.NewDict() + params.Set("disk_id", jsonutils.NewString(diskId)) + _, err := modules.Snapshots.PerformClassAction( + hostutils.GetComputeSession(context.Background()), + "delete-disk-snapshots", params) + if err != nil { + log.Infof("Request delele disk %s snapshots failed %s", diskId, err) + } + return + } + + snapshots, err := ioutil.ReadDir(snapshotPath) + if err != nil { + log.Errorln(err) + return + } + + // if snapshot count greater than maxsnapshot count, do convert + if len(snapshots) >= maxSnapshotCount { + s.requestConvertSnapshot(snapshotPath, diskId) + } +} + +func (s *SLocalStorage) requestConvertSnapshot(snapshotPath, diskId string) { + res, err := modules.Disks.GetSpecific( + hostutils.GetComputeSession(context.Background()), diskId, "convert-snapshot", nil) + if err != nil { + log.Errorln(err) + return + } + + var ( + deleteSnapshot, _ = res.GetString("delete_snapshot") + convertSnapshot, _ = res.GetString("convert_snapshot") + pendingDelete, _ = res.Bool("pending_delete") + ) + log.Infof("start convert disk(%s) snapshot(%s), delete_snapshot is %s", + diskId, convertSnapshot, deleteSnapshot) + convertSnapshotPath := path.Join(snapshotPath, convertSnapshot) + outfile := convertSnapshotPath + ".tmp" + img, err := qemuimg.NewQemuImage(convertSnapshot) + if err != nil { + log.Errorln(err) + return + } + err = img.Convert2Qcow2To(outfile, true) + if err != nil { + log.Errorln(err) + return + } + s.requestDeleteSnapshot( + diskId, snapshotPath, deleteSnapshot, convertSnapshotPath, outfile, pendingDelete) +} + +func (s *SLocalStorage) requestDeleteSnapshot( + diskId, snapshotPath, deleteSnapshot, convertSnapshotPath, + outfile string, pendingDelete bool, +) { + deleteSnapshotPath := path.Join(snapshotPath, deleteSnapshot) + DELETEING_SNAPSHOTS[diskId] = true + defer delete(DELETEING_SNAPSHOTS, diskId) + _, err := modules.Snapshots.PerformAction(hostutils.GetComputeSession(context.Background()), + deleteSnapshot, "deleted", nil) + if err != nil { + log.Errorln(err) + return + } + if out, err := procutils.NewCommand("rm", "-f", convertSnapshotPath).Run(); err != nil { + log.Errorf("%s", out) + return + } + if out, err := procutils.NewCommand("mv", "-f", outfile, convertSnapshotPath).Run(); err != nil { + log.Errorf("%s", out) + return + } + if !pendingDelete { + if out, err := procutils.NewCommand("rm", "-f", deleteSnapshotPath).Run(); err != nil { + log.Errorf("%s", out) + return + } + } +} + +/******************************* END *****************************/ diff --git a/pkg/hostman/system_service/doc.go b/pkg/hostman/system_service/doc.go new file mode 100644 index 0000000000..6c2526467f --- /dev/null +++ b/pkg/hostman/system_service/doc.go @@ -0,0 +1 @@ +package system_service // import "yunion.io/x/onecloud/pkg/hostman/system_service" diff --git a/pkg/hostman/system_service/fluentbit.go b/pkg/hostman/system_service/fluentbit.go new file mode 100644 index 0000000000..df2bf43f00 --- /dev/null +++ b/pkg/hostman/system_service/fluentbit.go @@ -0,0 +1,72 @@ +package system_service + +import ( + "fmt" + "net/url" + "strings" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/hostman/options" +) + +type SFluentbit struct { + *SBaseSystemService +} + +func NewFluentbitService() *SFluentbit { + return &SFluentbit{&SBaseSystemService{"fluentbit", nil}} +} + +func (s *SFluentbit) GetConfig(kwargs map[string]interface{}) string { + // 写到这 + conf := "" + conf += "[SERVICE]\n" + conf += " Flush 5\n" + conf += " Daemon Off\n" + conf += " Log_Level info\n" + conf += " Parsers_File parsers.conf\n" + conf += " Plugins_File plugins.conf\n" + conf += " HTTP_Server Off\n" + conf += "\n" + + conf += "[INPUT]\n" + conf += " Name systemd\n" + for _, u := range options.HostOptions.LogSystemdUnits { + conf += fmt.Sprintf(" Systemd_Filter _SYSTEMD_UNIT=%s.service\n", u) + } + conf += " Tag host.*\n" + conf += "\n" + + ielUrl, _ := kwargs["elasticsearch"] + mesUrl, _ := ielUrl.(map[string]string) + sesUrl, _ := mesUrl["url"] + esurl, err := url.Parse(sesUrl) + if err != nil { + log.Errorln(err) + return "" + } + esHostname := strings.Split(esurl.Host, ":")[0] + conf += "[OUTPUT]\n" + conf += " Name es\n" + conf += " Match *\n" + conf += fmt.Sprintf(" Host %s\n", esHostname) + conf += " Port 9200\n" + conf += " Logstash_Format on\n" + conf += " Retry_Limit False\n" + conf += " Type flb_type\n" + conf += " Time_Key @timestamp\n" + conf += " Logstash_Prefix onecloud\n" + return conf +} + +func (s *SFluentbit) GetConfigFile() string { + return "/etc/fluent-bit/fluent-bit.conf" +} + +func (s *SFluentbit) Reload(kwargs map[string]interface{}) error { + return s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} + +func (s *SFluentbit) BgReload(kwargs map[string]interface{}) { + go s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} diff --git a/pkg/hostman/system_service/kube_agent.go b/pkg/hostman/system_service/kube_agent.go new file mode 100644 index 0000000000..2762f463af --- /dev/null +++ b/pkg/hostman/system_service/kube_agent.go @@ -0,0 +1,31 @@ +package system_service + +import "fmt" + +type SKubeAgent struct { + *SBaseSystemService +} + +func NewKubeAgentService() *SKubeAgent { + return &SKubeAgent{&SBaseSystemService{"yunion-kube-agent", nil}} +} + +func (s *SKubeAgent) GetConfig(kwargs map[string]interface{}) string { + conf := "" + conf += fmt.Sprintf("server = \"%s\"\n", kwargs["serverUrl"]) + conf += fmt.Sprintf("token = \"%s\"\n", kwargs["token"]) + conf += fmt.Sprintf("id = \"%s\"\n", kwargs["nodeId"]) + return conf +} + +func (s *SKubeAgent) GetConfigFile() string { + return "/etc/yunion/kube-agent.conf" +} + +func (s *SKubeAgent) Reload(kwargs map[string]interface{}) error { + return s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} + +func (s *SKubeAgent) BgReload(kwargs map[string]interface{}) { + go s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} diff --git a/pkg/hostman/system_service/ntpd.go b/pkg/hostman/system_service/ntpd.go new file mode 100644 index 0000000000..3e43d3d02a --- /dev/null +++ b/pkg/hostman/system_service/ntpd.go @@ -0,0 +1,52 @@ +package system_service + +import "fmt" + +type SNtpd struct { + *SBaseSystemService +} + +func NewNtpdService() *SNtpd { + return &SNtpd{&SBaseSystemService{"ntpd", nil}} +} + +func (s *SNtpd) GetConfig(kwargs map[string]interface{}) string { + var srvs = []string{} + if servers, ok := kwargs["servers"]; ok { + ss, _ := servers.([]string) + for _, srv := range ss { + srvs = append(srvs, srv[len("ntp://"):]) + } + } else { + srvs = []string{"1.cn.pool.ntp.org", + "2.cn.pool.ntp.org", + "3.cn.pool.ntp.org", + "0.cn.pool.ntp.org", + "cn.pool.ntp.org"} + } + + conf := "" + conf += "driftfile /var/lib/ntp/drift\n" + conf += "restrict default nomodify notrap nopeer noquery kod limited\n" + conf += "restrict 127.0.0.1\n" + conf += "restrict ::1\n" + for _, srv := range srvs { + conf += fmt.Sprintf("server %s iburst\n", srv) + } + conf += "includefile /etc/ntp/crypto/pw\n" + conf += "keys /etc/ntp/keys\n" + conf += "disable monitor\n" + return conf +} + +func (s *SNtpd) GetConfigFile() string { + return "/etc/ntp.conf" +} + +func (s *SNtpd) Reload(kwargs map[string]interface{}) error { + return s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} + +func (s *SNtpd) BgReload(kwargs map[string]interface{}) { + go s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} diff --git a/pkg/hostman/system_service/openvswitch.go b/pkg/hostman/system_service/openvswitch.go new file mode 100644 index 0000000000..add56f0ace --- /dev/null +++ b/pkg/hostman/system_service/openvswitch.go @@ -0,0 +1,17 @@ +package system_service + +type SOpenvswitch struct { + *SBaseSystemService +} + +func NewOpenvswitchService() *SOpenvswitch { + return &SOpenvswitch{&SBaseSystemService{"openvswitch", nil}} +} + +func (s *SOpenvswitch) Reload(kwargs map[string]interface{}) error { + return s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} + +func (s *SOpenvswitch) BgReload(kwargs map[string]interface{}) { + go s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} diff --git a/pkg/hostman/system_service/sdnagent.go b/pkg/hostman/system_service/sdnagent.go new file mode 100644 index 0000000000..bae82d37e6 --- /dev/null +++ b/pkg/hostman/system_service/sdnagent.go @@ -0,0 +1,17 @@ +package system_service + +type SHostSdnagent struct { + *SBaseSystemService +} + +func NewHostSdnagentService() *SHostSdnagent { + return &SHostSdnagent{&SBaseSystemService{"yunion-host-sdnagent", nil}} +} + +func (s *SHostSdnagent) Reload(kwargs map[string]interface{}) error { + return s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} + +func (s *SHostSdnagent) BgReload(kwargs map[string]interface{}) { + go s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} diff --git a/pkg/hostman/system_service/system_service.go b/pkg/hostman/system_service/system_service.go new file mode 100644 index 0000000000..31cfa2ec09 --- /dev/null +++ b/pkg/hostman/system_service/system_service.go @@ -0,0 +1,148 @@ +package system_service + +import ( + "strings" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +type ISystemService interface { + IsInstalled() bool + Start(enable bool) error + Stop(disable bool) error + IsActive() bool + GetConfig(map[string]interface{}) string + SetConf(interface{}) + GetConf() interface{} + BgReload(kwargs map[string]interface{}) + Enable() error + Disable() error + GetStatus() map[string]string + Reload(kwargs map[string]interface{}) error +} + +type NewServiceFunc func() + +var serviceMap = map[string]ISystemService{ + "ntpd": NewNtpdService(), + "telegraf": NewTelegrafService(), + "host_sdnagent": NewHostSdnagentService(), + "openvswitch": NewOpenvswitchService(), + "fluentbit": NewFluentbitService(), + "kube_agent": NewKubeAgentService(), +} + +func GetService(name string) ISystemService { + if service, ok := serviceMap[name]; ok { + return service + } else { + return nil + } +} + +type SBaseSystemService struct { + name string + urls interface{} +} + +func (s *SBaseSystemService) reload(conf, conFile string) error { + oldConf, err := fileutils2.FileGetContents(conFile) + if err != nil { + return err + } + if conf != oldConf { + log.Infof("Reload service %s ...", s.name) + err := fileutils2.FilePutContents(conFile, conf, false) + if err != nil { + return err + } + return s.Start(false) + } + return nil +} + +func (s *SBaseSystemService) IsInstalled() bool { + status := s.GetStatus() + if loaded, ok := status["loaded"]; ok && loaded == "loaded" { + return true + } + return false +} + +func (s *SBaseSystemService) GetStatus() map[string]string { + res, err := procutils.NewCommand("systemctl", "status", s.name).Run() + if err != nil { + log.Errorln(err) + return nil + } + + var ret = make(map[string]string, 0) + lines := strings.Split(string(res), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) > 0 { + if strings.HasPrefix(line, "Loaded:") { + ret["loaded"] = strings.Split(line, " ")[1] + } else if strings.HasPrefix(line, "Active:") { + ret["active"] = strings.Split(line, " ")[1] + } + } + } + return ret +} + +func (s *SBaseSystemService) Start(enable bool) error { + if enable { + if err := s.Enable(); err != nil { + return err + } + } + _, err := procutils.NewCommand("systemctl", "restart", s.name).Run() + return err +} + +func (s *SBaseSystemService) Stop(disable bool) error { + if disable { + if err := s.Disable(); err != nil { + return err + } + } + _, err := procutils.NewCommand("systemctl", "stop", s.name).Run() + return err +} + +func (s *SBaseSystemService) IsActive() bool { + status := s.GetStatus() + if active, ok := status["active"]; ok && active == "active" { + return true + } + return false +} + +func (s *SBaseSystemService) GetConfig(map[string]interface{}) string { + return "" +} + +func (s *SBaseSystemService) GetConfigFile() string { + return "" +} + +func (s *SBaseSystemService) SetConf(urls interface{}) { + s.urls = urls +} + +func (s *SBaseSystemService) GetConf() interface{} { + return s.urls +} + +func (s *SBaseSystemService) Enable() error { + _, err := procutils.NewCommand("systemctl", "enable", s.name).Run() + return err +} + +func (s *SBaseSystemService) Disable() error { + _, err := procutils.NewCommand("systemctl", "disable", s.name).Run() + return err +} diff --git a/pkg/hostman/system_service/telegraf.go b/pkg/hostman/system_service/telegraf.go new file mode 100644 index 0000000000..b93a3399af --- /dev/null +++ b/pkg/hostman/system_service/telegraf.go @@ -0,0 +1,167 @@ +package system_service + +import ( + "fmt" + "strings" +) + +type STelegraf struct { + *SBaseSystemService +} + +func NewTelegrafService() *STelegraf { + return &STelegraf{&SBaseSystemService{"telegraf", nil}} +} + +func (s *STelegraf) GetConfig(kwargs map[string]interface{}) string { + conf := "" + conf += "[global_tags]\n" + if tags, ok := kwargs["tags"]; ok { + tgs, _ := tags.(map[string]string) + for k, v := range tgs { + conf += fmt.Sprintf(" %s = \"%s\"\n", k, v) + } + } + conf += "\n" + conf += "[agent]\n" + conf += " interval = \"60s\"\n" + conf += " round_interval = true\n" + conf += " metric_batch_size = 1000\n" + conf += " metric_buffer_limit = 10000\n" + conf += " collection_jitter = \"0s\"\n" + conf += " flush_interval = \"60s\"\n" + conf += " flush_jitter = \"0s\"\n" + conf += " precision = \"\"\n" + conf += " debug = false\n" + conf += " quiet = false\n" + conf += " logfile = \"/var/log/telegraf/telegraf.err.log\"\n" + var hostname string + if hn, ok := kwargs["hostname"]; ok { + hostname, _ = hn.(string) + } + conf += fmt.Sprintf(" hostname = \"%s\"\n", hostname) + conf += " omit_hostname = false\n" + conf += "\n" + if ifluxb, ok := kwargs["influxdb"]; ok { + influxdb, _ := ifluxb.(map[string]interface{}) + inUrls, _ := influxdb["url"] + tUrls, _ := inUrls.([]string) + inDatabase, _ := influxdb["database"] + tdb, _ := inDatabase.(string) + urls := []string{} + for _, u := range tUrls { + urls = append(urls, fmt.Sprintf("\"%s\"", u)) + } + conf += "[[outputs.influxdb]]\n" + conf += fmt.Sprintf(" urls = [%s]\n", strings.Join(urls, ", ")) + conf += fmt.Sprintf(" database = \"%s\"\n", tdb) + conf += " retention_policy = \"autogen\"\n" + conf += " insecure_skip_verify = true\n" + conf += "\n" + } + if kafka, ok := kwargs["kafka"]; ok { + ka, _ := kafka.(map[string]interface{}) + bks, _ := ka["brokers"] + tbk, _ := bks.([]string) + brokers := []string{} + for _, b := range tbk { + brokers = append(brokers, fmt.Sprintf("\"%s\"", b[len("kafka://\n"):])) + } + conf += "[[outputs.kafka]]\n" + conf += fmt.Sprintf(" brokers = [%s]\n", strings.Join(brokers, ", ")) + + topic, _ := ka["topic"] + itopic, _ := topic.(string) + conf += fmt.Sprintf(" topic = \"%s\"\n", itopic) + conf += " compression_codec = 0\n" + conf += " required_acks = -1\n" + conf += " max_retry = 3\n" + conf += " data_format = \"json\"\n" + conf += " json_timestamp_units = \"1ms\"\n" + conf += " routing_tag = \"host\"\n" + conf += "\n" + } + conf += "[[inputs.cpu]]\n" + conf += " percpu = false\n" + conf += " totalcpu = true\n" + conf += " collect_cpu_time = false\n" + conf += " report_active = true\n" + conf += "\n" + conf += "[[inputs.disk]]\n" + conf += " ignore_fs = [\"tmpfs\", \"devtmpfs\", \"overlay\", \"squashfs\", \"iso9660\"]\n" + conf += "\n" + conf += "[[inputs.diskio]]\n" + conf += " skip_serial_number = false\n" + conf += " excludes = \"^nbd\"\n" + conf += "\n" + conf += "[[inputs.kernel]]\n" + conf += "\n" + conf += "[[inputs.kernel_vmstat]]\n" + conf += "\n" + conf += "[[inputs.mem]]\n" + conf += "\n" + conf += "[[inputs.processes]]\n" + conf += "\n" + conf += "[[inputs.swap]]\n" + conf += "\n" + conf += "[[inputs.system]]\n" + conf += "\n" + conf += "[[inputs.net]]\n" + if nics, ok := kwargs["nics"]; ok { + ns, _ := nics.([]map[string]interface{}) + infs := []string{} + for _, n := range ns { + iname, _ := n["name"] + name, _ := iname.(string) + infs = append(infs, fmt.Sprintf("\"%s\"", name)) + } + conf += fmt.Sprintf(" interfaces = [%s]\n", strings.Join(infs, ", ")) + conf += "\n" + for _, n := range ns { + iname, _ := n["name"] + name, _ := iname.(string) + ialias, _ := n["alias"] + alias, _ := ialias.(string) + ispeed, _ := n["speed"] + speed, _ := ispeed.(int) + + conf += " [[inputs.net.interface_conf]]\n" + conf += fmt.Sprintf(" name = \"%s\"\n", name) + conf += fmt.Sprintf(" alias = \"%s\"\n", alias) + conf += fmt.Sprintf(" speed = %d\n", speed) + conf += "\n" + } + } + conf += "[[inputs.netstat]]\n" + conf += "\n" + conf += "[[inputs.nstat]]\n" + conf += "\n" + conf += "[[inputs.ntpq]]\n" + conf += " dns_lookup = false\n" + conf += "\n" + if pidFile, ok := kwargs["pid_file"]; ok { + pf, _ := pidFile.(string) + conf += "[[inputs.procstat]]\n" + conf += fmt.Sprintf(" pid_file = \"%s\"\n", pf) + conf += "\n" + } + conf += "[[inputs.internal]]\n" + conf += " collect_memstats = false\n" + conf += "\n" + conf += "[[inputs.http_listener]]\n" + conf += " service_address = \"localhost:8087\"\n" + conf += "\n" + return conf +} + +func (s *STelegraf) GetConfigFile() string { + return "/etc/telegraf/telegraf.conf" +} + +func (s *STelegraf) Reload(kwargs map[string]interface{}) error { + return s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} + +func (s *STelegraf) BgReload(kwargs map[string]interface{}) { + go s.reload(s.GetConfig(kwargs), s.GetConfigFile()) +} diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index 11d91c307f..707067ea19 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -533,7 +533,7 @@ func (self *SImage) CustomizeDelete(ctx context.Context, userCred mcclient.Token overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false) purge = jsonutils.QueryBoolean(query, "purge", false) } - if self.Status != IMAGE_STATUS_ACTIVE { + if self.Status != IMAGE_STATUS_ACTIVE && self.Status != IMAGE_STATUS_CONVERTING { overridePendingDelete = true } return self.startDeleteImageTask(ctx, userCred, "", purge, overridePendingDelete) @@ -827,6 +827,7 @@ func (self *SImage) MakeSubImages() error { if self.GetImageType() == ImageTypeISO { return nil } + log.Debugf("[MakeSubImages] convert image to %#v", options.Options.TargetImageFormats) for _, format := range options.Options.TargetImageFormats { if !qemuimg.IsSupportedImageFormat(format) { continue @@ -848,6 +849,9 @@ func (self *SImage) MakeSubImages() error { func (self *SImage) ConvertAllSubformats() error { subimgs := ImageSubformatManager.GetAllSubImages(self.Id) for i := 0; i < len(subimgs); i += 1 { + if !utils.IsInStringArray(subimgs[i].Format, options.Options.TargetImageFormats) { + continue + } err := subimgs[i].DoConvert(self) if err != nil { return err @@ -1016,7 +1020,7 @@ func (self *SImage) DoCheckStatus(ctx context.Context, userCred mcclient.TokenCr } for i := 0; i < len(subimgs); i += 1 { subimgs[i].checkStatus(useFast) - if subimgs[i].Status != IMAGE_STATUS_ACTIVE || subimgs[i].TorrentStatus != IMAGE_STATUS_ACTIVE { + if (subimgs[i].Status != IMAGE_STATUS_ACTIVE || subimgs[i].TorrentStatus != IMAGE_STATUS_ACTIVE) && utils.IsInStringArray(subimgs[i].Format, options.Options.TargetImageFormats) { needConvert = true } } diff --git a/pkg/image/options/options.go b/pkg/image/options/options.go index 2a3a7e76ee..7f07c220b0 100644 --- a/pkg/image/options/options.go +++ b/pkg/image/options/options.go @@ -12,7 +12,7 @@ type SImageOptions struct { pending_delete.SPendingDeleteOptions - DefaultImageQuota int `default:"5" help:"Common image quota per tenant, default 5"` + DefaultImageQuota int `default:"10" help:"Common image quota per tenant, default 10"` PortV2 int `help:"Listening port for region V2"` diff --git a/pkg/image/service/service.go b/pkg/image/service/service.go index 2daa6d0675..8e891f01dd 100644 --- a/pkg/image/service/service.go +++ b/pkg/image/service/service.go @@ -81,12 +81,12 @@ func StartService() { go models.CheckImages() - cron := cronman.GetCronJobManager() + cron := cronman.GetCronJobManager(true) cron.AddJob1("CleanPendingDeleteImages", time.Duration(options.Options.PendingDeleteCheckSeconds)*time.Second, models.ImageManager.CleanPendingDeleteImages) cron.Start() - cloudcommon.ServeForever(app, commonOpts, func() { + cloudcommon.ServeForeverWithCleanup(app, commonOpts, func() { cloudcommon.CloseDB() cron.Stop() diff --git a/pkg/logger/models/actionlog.go b/pkg/logger/models/actionlog.go new file mode 100644 index 0000000000..993c52b17a --- /dev/null +++ b/pkg/logger/models/actionlog.go @@ -0,0 +1,34 @@ +package models + +import ( + "context" + "time" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SActionlogManager struct { + db.SOpsLogManager +} + +type SActionlog struct { + db.SOpsLog + + // StartTime time.Time `nullable:"false" list:"user"` // = Column(DateTime, nullable=False) + Success bool `default:"true" list:"user" create:"required"` // = Column(Boolean, default=True) + // Action string `width:"32" charset:"utf8" nullable:"false" list:"user"` //= Column(VARCHAR(32, charset='utf8'), nullable=False) +} + +var ActonLog *SActionlogManager + +func init() { + ActonLog = &SActionlogManager{db.SOpsLogManager{db.NewModelBaseManager(SActionlog{}, "action_tbl", "action", "actions")}} +} + +func (action *SActionlog) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + action.OpsTime = time.Now().UTC() + return nil +} diff --git a/pkg/logger/models/doc.go b/pkg/logger/models/doc.go new file mode 100644 index 0000000000..84b4a7b0fa --- /dev/null +++ b/pkg/logger/models/doc.go @@ -0,0 +1 @@ +package models // import "yunion.io/x/onecloud/pkg/logger/models" diff --git a/pkg/logger/options/doc.go b/pkg/logger/options/doc.go new file mode 100644 index 0000000000..d7fc122338 --- /dev/null +++ b/pkg/logger/options/doc.go @@ -0,0 +1 @@ +package options // import "yunion.io/x/onecloud/pkg/logger/options" diff --git a/pkg/logger/options/options.go b/pkg/logger/options/options.go new file mode 100644 index 0000000000..13c82de5a7 --- /dev/null +++ b/pkg/logger/options/options.go @@ -0,0 +1,15 @@ +package options + +import ( + "yunion.io/x/onecloud/pkg/cloudcommon" +) + +type SLoggerOptions struct { + cloudcommon.CommonOptions + + cloudcommon.DBOptions +} + +var ( + Options SLoggerOptions +) diff --git a/pkg/logger/service/doc.go b/pkg/logger/service/doc.go new file mode 100644 index 0000000000..88bdeb100f --- /dev/null +++ b/pkg/logger/service/doc.go @@ -0,0 +1 @@ +package service // import "yunion.io/x/onecloud/pkg/logger/service" diff --git a/pkg/logger/service/handlers.go b/pkg/logger/service/handlers.go new file mode 100644 index 0000000000..854864f19f --- /dev/null +++ b/pkg/logger/service/handlers.go @@ -0,0 +1,27 @@ +package service + +import ( + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/appsrv/dispatcher" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/logger/models" +) + +func initHandlers(app *appsrv.Application) { + db.InitAllManagers() + + for _, manager := range []db.IModelManager{ + // db.UserCacheManager, + db.TenantCacheManager, + } { + db.RegisterModelManager(manager) + } + + for _, manager := range []db.IModelManager{ + models.ActonLog, + } { + db.RegisterModelManager(manager) + handler := db.NewModelHandler(manager) + dispatcher.AddModelDispatcher("", app, handler) + } +} diff --git a/pkg/logger/service/service.go b/pkg/logger/service/service.go new file mode 100644 index 0000000000..ec2ddccdae --- /dev/null +++ b/pkg/logger/service/service.go @@ -0,0 +1,44 @@ +package service + +import ( + "os" + + _ "github.com/go-sql-driver/mysql" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon" + "yunion.io/x/onecloud/pkg/cloudcommon/consts" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/logger/options" +) + +const ( + SERVICE_TYPE = "log" +) + +func StartService() { + + consts.DisableOpsLog() + + opts := &options.Options + commonOpts := &opts.CommonOptions + dbOpts := &opts.DBOptions + cloudcommon.ParseOptions(opts, os.Args, "log.conf", SERVICE_TYPE) + + cloudcommon.InitAuth(commonOpts, func() { + log.Infof("Auth complete!!") + }) + + cloudcommon.InitDB(dbOpts) + defer cloudcommon.CloseDB() + + app := cloudcommon.InitApp(commonOpts, true) + initHandlers(app) + + if !db.CheckSync(opts.AutoSyncTable) { + log.Fatalf("database schema not in sync!") + } + + cloudcommon.ServeForever(app, commonOpts) +} diff --git a/pkg/mcclient/models/loadbalancers.go b/pkg/mcclient/models/loadbalancers.go index ca6a7961dd..2c1b2cc6be 100644 --- a/pkg/mcclient/models/loadbalancers.go +++ b/pkg/mcclient/models/loadbalancers.go @@ -15,8 +15,10 @@ type Loadbalancer struct { VpcId string ZoneId string - BackendGroupId string - CloudregionId string + BackendGroupId string + CloudregionId string + ChargeType string + LoadbalancerSpec string } type LoadbalancerTCPListener struct{} @@ -46,7 +48,9 @@ type LoadbalancerHTTPRateLimiter struct { type LoadbalancerListener struct { VirtualResource + ManagedResource + CloudregionId string LoadbalancerId string ListenerType string ListenerPort int @@ -58,7 +62,8 @@ type LoadbalancerListener struct { BackendConnectTimeout int BackendIdleTimeout int - BackendGroupId string + BackendGroupId string + BackendServerPort int AclStatus string AclType string @@ -89,7 +94,9 @@ type LoadbalancerListener struct { type LoadbalancerListenerRule struct { VirtualResource + ManagedResource + CloudregionId string ListenerId string BackendGroupId string @@ -101,14 +108,18 @@ type LoadbalancerListenerRule struct { type LoadbalancerBackendGroup struct { VirtualResource + ManagedResource Type string LoadbalancerId string + CloudregionId string } type LoadbalancerBackend struct { VirtualResource + ManagedResource + CloudregionId string BackendGroupId string BackendId string BackendType string diff --git a/pkg/mcclient/modules/joint.go b/pkg/mcclient/modules/joint.go index fad9f107cd..4e69e7d10b 100644 --- a/pkg/mcclient/modules/joint.go +++ b/pkg/mcclient/modules/joint.go @@ -14,6 +14,8 @@ type JointResourceManager struct { Slave Manager } +var _ JointManager = (*JointResourceManager)(nil) + func (this *JointResourceManager) MasterManager() Manager { return this.Master } @@ -116,8 +118,14 @@ func (this *JointResourceManager) BatchAttach2(s *mcclient.ClientSession, mid st }) } -func (this *JointResourceManager) Detach(s *mcclient.ClientSession, mid, sid string) (jsonutils.JSONObject, error) { +func (this *JointResourceManager) Detach(s *mcclient.ClientSession, mid, sid string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { path := fmt.Sprintf("/%s/%s/%s/%s", this.Master.KeyString(), url.PathEscape(mid), this.Slave.KeyString(), url.PathEscape(sid)) + if query != nil { + qs := query.QueryString() + if len(qs) > 0 { + path = fmt.Sprintf("%s?%s", path, qs) + } + } result, err := this._delete(s, path, nil, this.Keyword) if err != nil { return nil, err @@ -127,18 +135,24 @@ func (this *JointResourceManager) Detach(s *mcclient.ClientSession, mid, sid str func (this *JointResourceManager) BatchDetach(s *mcclient.ClientSession, mid string, sids []string) []SubmitResult { return BatchDo(sids, func(sid string) (jsonutils.JSONObject, error) { - return this.Detach(s, mid, sid) + return this.Detach(s, mid, sid, nil) }) } func (this *JointResourceManager) BatchDetach2(s *mcclient.ClientSession, mid string, sids []string) []SubmitResult { return BatchDo(sids, func(sid string) (jsonutils.JSONObject, error) { - return this.Detach(s, sid, mid) + return this.Detach(s, sid, mid, nil) }) } -func (this *JointResourceManager) Update(s *mcclient.ClientSession, mid, sid string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { +func (this *JointResourceManager) Update(s *mcclient.ClientSession, mid, sid string, query jsonutils.JSONObject, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { path := fmt.Sprintf("/%s/%s/%s/%s", this.Master.KeyString(), url.PathEscape(mid), this.Slave.KeyString(), url.PathEscape(sid)) + if query != nil { + queryStr := query.QueryString() + if len(queryStr) > 0 { + path = fmt.Sprintf("%s?%s", path, queryStr) + } + } result, err := this._put(s, path, this.params2Body(s, params), this.Keyword) if err != nil { return nil, err @@ -146,8 +160,14 @@ func (this *JointResourceManager) Update(s *mcclient.ClientSession, mid, sid str return this.filterSingleResult(s, result, nil) } -func (this *JointResourceManager) Patch(s *mcclient.ClientSession, mid, sid string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { +func (this *JointResourceManager) Patch(s *mcclient.ClientSession, mid, sid string, query jsonutils.JSONObject, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { path := fmt.Sprintf("/%s/%s/%s/%s", this.Master.KeyString(), url.PathEscape(mid), this.Slave.KeyString(), url.PathEscape(sid)) + if query != nil { + queryStr := query.QueryString() + if len(queryStr) > 0 { + path = fmt.Sprint("%s?%s", path, queryStr) + } + } result, err := this._patch(s, path, this.params2Body(s, params), this.Keyword) if err != nil { return nil, err diff --git a/pkg/mcclient/modules/managers.go b/pkg/mcclient/modules/managers.go index 12c2c51691..5fa631f235 100644 --- a/pkg/mcclient/modules/managers.go +++ b/pkg/mcclient/modules/managers.go @@ -126,6 +126,14 @@ func NewMeterManager(keyword, keywordPlural string, columns, adminColumns []stri Keyword: keyword, KeywordPlural: keywordPlural} } +func NewMeterAlertManager(keyword, keywordPlural string, columns, adminColumns []string) ResourceManager { + return ResourceManager{ + BaseManager: BaseManager{columns: columns, + adminColumns: adminColumns, + serviceType: "meteralert"}, + Keyword: keyword, KeywordPlural: keywordPlural} +} + func NewYunionAgentManager(keyword, keywordPlural string, columns, adminColumns []string) ResourceManager { return ResourceManager{ BaseManager: BaseManager{columns: columns, diff --git a/pkg/mcclient/modules/mod_cachedimages.go b/pkg/mcclient/modules/mod_cachedimages.go index 160aaaa51a..8915b21462 100644 --- a/pkg/mcclient/modules/mod_cachedimages.go +++ b/pkg/mcclient/modules/mod_cachedimages.go @@ -6,7 +6,12 @@ var ( func init() { Cachedimages = NewComputeManager("cachedimage", "cachedimages", - []string{"ID", "Name", "Size", "Format", "Owner", "OS_Type", "OS_Distribution", "OS_version", "Hypervisor", "Host_count", "Status"}, + []string{"ID", "Name", "Size", "Format", "Owner", + "OS_Type", "OS_Distribution", "OS_version", + "Hypervisor", "Host_count", "Status", + "Ref_Count", "cached_count", "image_type", + "External_Id", + }, []string{}) registerCompute(&Cachedimages) diff --git a/pkg/mcclient/modules/mod_cloudregions.go b/pkg/mcclient/modules/mod_cloudregions.go index a2aad96493..c8919290e3 100644 --- a/pkg/mcclient/modules/mod_cloudregions.go +++ b/pkg/mcclient/modules/mod_cloudregions.go @@ -1,16 +1,86 @@ package modules -var ( - Cloudregions ResourceManager +import ( + "yunion.io/x/jsonutils" + + "sort" + "yunion.io/x/onecloud/pkg/mcclient" ) +type SCloudregionManager struct { + ResourceManager +} + +var ( + Cloudregions SCloudregionManager +) + +type sNameCounter struct { + Name string + Count int +} + +type tNameCounters []sNameCounter + +func (cc tNameCounters) Len() int { return len(cc) } +func (cc tNameCounters) Swap(i, j int) { cc[i], cc[j] = cc[j], cc[i] } +func (cc tNameCounters) Less(i, j int) bool { + if cc[i].Count != cc[j].Count { + return cc[i].Count > cc[j].Count + } + return cc[i].Name < cc[j].Name +} + +func (this *SCloudregionManager) getRegionAttributeList(session *mcclient.ClientSession, params jsonutils.JSONObject, attr string) (jsonutils.JSONObject, error) { + paramsDict := params.(*jsonutils.JSONDict) + paramsDict.Set("limit", jsonutils.NewInt(0)) + + listResult, err := this.List(session, params) + if err != nil { + return nil, err + } + + cities := make(map[string]int) + for i := range listResult.Data { + cityStr, _ := listResult.Data[i].GetString(attr) + if len(cityStr) > 0 { + if _, ok := cities[cityStr]; ok { + cities[cityStr] += 1 + } else { + cities[cityStr] = 1 + } + } + } + + cityList := make([]sNameCounter, len(cities)) + i := 0 + for k, v := range cities { + cityList[i] = sNameCounter{Name: k, Count: v} + i += 1 + } + + sort.Sort(tNameCounters(cityList)) + + return jsonutils.Marshal(cityList), nil +} + +func (this *SCloudregionManager) GetRegionCities(session *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return this.getRegionAttributeList(session, params, "city") +} + +func (this *SCloudregionManager) GetRegionProviders(session *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return this.getRegionAttributeList(session, params, "provider") +} + func init() { - Cloudregions = NewComputeManager("cloudregion", "cloudregions", - []string{"ID", "Name", "Enabled", "Status", "Provider", - "Latitude", "Longitude", "City", "Country_Code", - "vpc_count", "zone_count", "guest_count", "guest_increment_count", - "External_Id"}, - []string{}) + Cloudregions = SCloudregionManager{ + NewComputeManager("cloudregion", "cloudregions", + []string{"ID", "Name", "Enabled", "Status", "Provider", + "Latitude", "Longitude", "City", "Country_Code", + "vpc_count", "zone_count", "guest_count", "guest_increment_count", + "External_Id"}, + []string{}), + } registerCompute(&Cloudregions) } diff --git a/pkg/mcclient/modules/mod_images.go b/pkg/mcclient/modules/mod_images.go index 7f19df3aa0..ea1bbfa10a 100644 --- a/pkg/mcclient/modules/mod_images.go +++ b/pkg/mcclient/modules/mod_images.go @@ -73,25 +73,7 @@ func (this *ImageManager) GetById(session *mcclient.ClientSession, id string, pa } func (this *ImageManager) GetByName(session *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - var dict *jsonutils.JSONDict - if params == nil { - dict = jsonutils.NewDict() - } else { - dict, _ = params.(*jsonutils.JSONDict) - } - dict.Add(jsonutils.NewString(id), "name") - dict.Add(jsonutils.JSONTrue, "details") - listresults, e := this.List(session, dict) - if e != nil { - return nil, e - } - if len(listresults.Data) == 0 { - return nil, httperrors.NewImageNotFoundError(id) - } else if len(listresults.Data) == 1 { - return listresults.Data[0], nil - } else { - return nil, httperrors.NewDuplicateNameError("image name", id) - } + return this.GetById(session, id, params) } func (this *ImageManager) Get(session *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { diff --git a/pkg/mcclient/modules/mod_loadbalancernetworks.go b/pkg/mcclient/modules/mod_loadbalancernetworks.go new file mode 100644 index 0000000000..45185c7cca --- /dev/null +++ b/pkg/mcclient/modules/mod_loadbalancernetworks.go @@ -0,0 +1,17 @@ +package modules + +var ( + Loadbalancernetworks JointResourceManager +) + +func init() { + Loadbalancernetworks = NewJointComputeManager( + "loadbalancernetwork", + "loadbalancernetworks", + []string{"Loadbalancer_ID", "Loadbalancer", + "Network_ID", "Network", "Ip_Addr"}, + []string{}, + &Loadbalancers, + &Networks) + registerCompute(&Loadbalancernetworks) +} diff --git a/pkg/mcclient/modules/mod_meteralert.go b/pkg/mcclient/modules/mod_meteralert.go new file mode 100644 index 0000000000..09e55db848 --- /dev/null +++ b/pkg/mcclient/modules/mod_meteralert.go @@ -0,0 +1,13 @@ +package modules + +var ( + MeterAlert ResourceManager +) + +func init() { + MeterAlert = NewMeterAlertManager("meteralert", "meteralerts", + []string{"id", "type", "provider", "account", "account_id", "comparator", "threshold", "recipients", "level", "channel", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark"}, + []string{}) + + register(&MeterAlert) +} diff --git a/pkg/mcclient/modules/mod_networks.go b/pkg/mcclient/modules/mod_networks.go index d80674e00d..443c2ee34e 100644 --- a/pkg/mcclient/modules/mod_networks.go +++ b/pkg/mcclient/modules/mod_networks.go @@ -10,8 +10,8 @@ func init() { "Guest_ip_end", "Guest_ip_mask", "wire_id", "wire", "is_public", "exit", "Ports", "vnics", "guest_gateway", - "group_vnics", "bm_vnics", "reserve_vnics", "server_type", - "Status"}, + "group_vnics", "bm_vnics", "reserve_vnics", "lb_vnics", + "server_type", "Status"}, []string{}) registerCompute(&Networks) diff --git a/pkg/mcclient/modules/mod_notification.go b/pkg/mcclient/modules/mod_notification.go deleted file mode 100644 index 71d0bb50d5..0000000000 --- a/pkg/mcclient/modules/mod_notification.go +++ /dev/null @@ -1,13 +0,0 @@ -package modules - -var ( - Notifications ResourceManager -) - -func init() { - Notifications = NewNotifyManager("notification", "notifications", - []string{"id", "uid", "contact_type", "topic", "priority", "msg", "received_at", "send_by", "status", "create_at", "update_at", "delete_at", "create_by", "update_by", "delete_by", "is_deleted", "remark"}, - []string{}) - - register(&Notifications) -} diff --git a/pkg/mcclient/modules/mod_schedtags.go b/pkg/mcclient/modules/mod_schedtags.go index a9667a62a6..f7d2ebf3b5 100644 --- a/pkg/mcclient/modules/mod_schedtags.go +++ b/pkg/mcclient/modules/mod_schedtags.go @@ -40,7 +40,7 @@ func (this *SchedtagManager) DoBatchSchedtagHostAddRemove(s *mcclient.ClientSess _host, _ := host.GetString() _tag, _ := tag.GetString() if action == "remove" { - Schedtaghosts.Detach(s, _tag, _host) + Schedtaghosts.Detach(s, _tag, _host, nil) } else if action == "add" { Schedtaghosts.Attach(s, _tag, _host, nil) } diff --git a/pkg/mcclient/modules/mod_tasks.go b/pkg/mcclient/modules/mod_tasks.go index 49040264b2..1717a96ca8 100644 --- a/pkg/mcclient/modules/mod_tasks.go +++ b/pkg/mcclient/modules/mod_tasks.go @@ -19,6 +19,19 @@ type ComputeTasksManager struct { ResourceManager } +func init() { + cols := []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"} + Tasks = NewITSMManager("task", "taskman", cols, cols) + register(&Tasks) + + ComputeTasks = ComputeTasksManager{ + ResourceManager: NewComputeManager("task", "tasks", + []string{}, + []string{"Id", "Obj_name", "Obj_Id", "Task_name", "Stage", "Created_at"}), + } + registerCompute(&ComputeTasks) +} + func (man ComputeTasksManager) TaskComplete(session *mcclient.ClientSession, taskId string, params jsonutils.JSONObject) { for i := 0; i < 3; i++ { _, err := man.PerformClassAction(session, taskId, params) @@ -32,23 +45,12 @@ func (man ComputeTasksManager) TaskComplete(session *mcclient.ClientSession, tas } func (man ComputeTasksManager) TaskFailed(session *mcclient.ClientSession, taskId string, err error) { + man.TaskFailed2(session, taskId, err.Error()) +} + +func (man ComputeTasksManager) TaskFailed2(session *mcclient.ClientSession, taskId string, reason string) { params := jsonutils.NewDict() params.Add(jsonutils.NewString("error"), "__status__") - params.Add(jsonutils.NewString(err.Error()), "__reason__") + params.Add(jsonutils.NewString(reason), "__reason__") man.TaskComplete(session, taskId, params) } - -func init() { - Tasks = NewITSMManager("task", "taskman", - []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"}, - []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"}, - ) - register(&Tasks) - - ComputeTasks = ComputeTasksManager{ - ResourceManager: NewComputeManager("task", "tasks", - []string{}, - []string{"Id", "Obj_name", "Obj_Id", "Task_name", "Stage", "Created_at"}), - } - registerCompute(&ComputeTasks) -} diff --git a/pkg/mcclient/modules/modules.go b/pkg/mcclient/modules/modules.go index 9304a20744..323420c63f 100644 --- a/pkg/mcclient/modules/modules.go +++ b/pkg/mcclient/modules/modules.go @@ -134,11 +134,11 @@ type JointManager interface { Attach(s *mcclient.ClientSession, mid, sid string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) BatchAttach(s *mcclient.ClientSession, mid string, sids []string, params jsonutils.JSONObject) []SubmitResult BatchAttach2(s *mcclient.ClientSession, mid string, sids []string, params jsonutils.JSONObject) []SubmitResult - Detach(s *mcclient.ClientSession, mid, sid string) (jsonutils.JSONObject, error) + Detach(s *mcclient.ClientSession, mid, sid string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) BatchDetach(s *mcclient.ClientSession, mid string, sids []string) []SubmitResult BatchDetach2(s *mcclient.ClientSession, mid string, sids []string) []SubmitResult - Update(s *mcclient.ClientSession, mid, sid string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) - Patch(s *mcclient.ClientSession, mid, sid string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + Update(s *mcclient.ClientSession, mid, sid string, query jsonutils.JSONObject, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + Patch(s *mcclient.ClientSession, mid, sid string, query jsonutils.JSONObject, params jsonutils.JSONObject) (jsonutils.JSONObject, error) } var ( diff --git a/pkg/mcclient/modules/notify/consts.go b/pkg/mcclient/modules/notify/consts.go new file mode 100644 index 0000000000..c4a1ece6d1 --- /dev/null +++ b/pkg/mcclient/modules/notify/consts.go @@ -0,0 +1,16 @@ +package notify + +type TNotifyPriority string + +type TNotifyChannel string + +const ( + NotifyPriorityImportant = TNotifyPriority("important") + NotifyPriorityCritical = TNotifyPriority("fatal") + NotifyPriorityNormal = TNotifyPriority("normal") + + NotifyByEmail = TNotifyChannel("email") + NotifyByMobile = TNotifyChannel("mobile") + NotifyByDingTalk = TNotifyChannel("dingtalk") + NotifyByWebConsole = TNotifyChannel("webconsole") +) diff --git a/pkg/mcclient/modules/notify/doc.go b/pkg/mcclient/modules/notify/doc.go new file mode 100644 index 0000000000..86ad9bf374 --- /dev/null +++ b/pkg/mcclient/modules/notify/doc.go @@ -0,0 +1 @@ +package notify // import "yunion.io/x/onecloud/pkg/mcclient/modules/notify" diff --git a/pkg/mcclient/modules/notify/mod_notification.go b/pkg/mcclient/modules/notify/mod_notification.go new file mode 100644 index 0000000000..dee91f722c --- /dev/null +++ b/pkg/mcclient/modules/notify/mod_notification.go @@ -0,0 +1,41 @@ +package notify + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + Notifications NotificationManager +) + +type SNotifyMessage struct { + Uid string `json:"uid,omitempty"` + Gid string `json:"uid,omitempty"` + ContactType []TNotifyChannel `json:"contact_type,omitempty"` + Topic string `json:"topic,omitempty"` + Priority TNotifyPriority `json:"priority,omitempty"` + Msg string `json:"msg,omitempty"` + Remark string `json:"remark,omitempty"` +} + +type NotificationManager struct { + modules.ResourceManager +} + +func (manager *NotificationManager) Send(s *mcclient.ClientSession, msg SNotifyMessage) error { + _, err := manager.Create(s, jsonutils.Marshal(&msg)) + return err +} + +func init() { + Notifications = NotificationManager{ + modules.NewNotifyManager("notification", "notifications", + []string{"id", "uid", "contact_type", "topic", "priority", "msg", "received_at", "send_by", "status", "create_at", "update_at", "delete_at", "create_by", "update_by", "delete_by", "is_deleted", "remark"}, + []string{}), + } + + modules.Register(&Notifications) +} diff --git a/pkg/mcclient/modules/notify/mod_notification_test.go b/pkg/mcclient/modules/notify/mod_notification_test.go new file mode 100644 index 0000000000..f9b87beb2c --- /dev/null +++ b/pkg/mcclient/modules/notify/mod_notification_test.go @@ -0,0 +1,22 @@ +package notify + +import ( + "testing" + + "yunion.io/x/jsonutils" +) + +func TestNotificationManager(t *testing.T) { + msg := SNotifyMessage{ + Uid: "testuser", + ContactType: []TNotifyChannel{ + NotifyByEmail, NotifyByWebConsole, + }, + Topic: "test message", + Priority: NotifyPriorityNormal, + Msg: "This is a test message. Yey!!", + Remark: "Yunion", + } + msgJson := jsonutils.Marshal(msg) + t.Logf("msg: %s", msgJson) +} diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index e8a6d12c09..f751bc64ee 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -71,22 +71,30 @@ func optionsStructRvToParams(rv reflect.Value) (*jsonutils.JSONDict, error) { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: rv64 := f.Convert(gotypes.Int64Type) i64 := rv64.Interface().(int64) - p.Set(name, jsonutils.NewInt(i64)) + if i64 != 0 || !jsonInfo.OmitZero { + p.Set(name, jsonutils.NewInt(i64)) + } case reflect.Bool: b := f.Interface().(bool) - p.Set(name, jsonutils.NewBool(b)) + if b || !jsonInfo.OmitFalse { + p.Set(name, jsonutils.NewBool(b)) + } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // NOTE uint64 converted to int64 rv64 := f.Convert(gotypes.Uint64Type) i64 := rv64.Interface().(uint64) - p.Set(name, jsonutils.NewInt(int64(i64))) + if i64 != 0 || !jsonInfo.OmitZero { + p.Set(name, jsonutils.NewInt(int64(i64))) + } case reflect.Float32, reflect.Float64: rv64 := f.Convert(gotypes.Float64Type) f64 := rv64.Interface().(float64) - p.Set(name, jsonutils.NewFloat(f64)) + if f64 != 0 || !jsonInfo.OmitZero { + p.Set(name, jsonutils.NewFloat(f64)) + } case reflect.String: s := f.Interface().(string) - if len(s) > 0 { + if len(s) > 0 || !jsonInfo.OmitEmpty { p.Set(name, jsonutils.NewString(s)) } case reflect.Struct: @@ -181,7 +189,7 @@ type BaseListOptions struct { Manager string `help:"List objects belonging to the cloud provider"` Account string `help:"List objects belonging to the cloud account"` - Provider string `help:"List objects from the provider" choices:"VMware|Aliyun|Qcloud|Azure|Aws|Huawei"` + Provider string `help:"List objects from the provider" choices:"VMware|Aliyun|Qcloud|Azure|Aws|Huawei|Openstack"` } func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) { diff --git a/pkg/mcclient/options/loadbalanceracls.go b/pkg/mcclient/options/loadbalanceracls.go index dc704c18fa..3064116497 100644 --- a/pkg/mcclient/options/loadbalanceracls.go +++ b/pkg/mcclient/options/loadbalanceracls.go @@ -56,8 +56,10 @@ func (entries AclEntries) String() string { } type LoadbalancerAclCreateOptions struct { - NAME string - AclEntry []string `help:"acl entry with cidr and comment separated by #, e.g. 10.9.0.0/16#no comment" json:"-"` + NAME string + AclEntry []string `help:"acl entry with cidr and comment separated by #, e.g. 10.9.0.0/16#no comment" json:"-"` + ManagerId string `json:"manager_id"` + Region string `json:"cloudregion"` } type LoadbalancerAclGetOptions struct { diff --git a/pkg/mcclient/options/loadbalancerbackendgroups.go b/pkg/mcclient/options/loadbalancerbackendgroups.go index 495acf870b..9b6798af21 100644 --- a/pkg/mcclient/options/loadbalancerbackendgroups.go +++ b/pkg/mcclient/options/loadbalancerbackendgroups.go @@ -1,8 +1,96 @@ package options +import ( + "fmt" + "strconv" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/pkg/utils" +) + type LoadbalancerBackendGroupCreateOptions struct { NAME string - Loadbalancer string + LOADBALANCER string + Type string `choices:"default|normal|master_slave"` + Backend []string `help:"backends with separated by ',' e.g. weight:80,port:443,id:01e9d393-d2b8-4d2e-85fb-023b83889070,backend_type:guest" json:"-"` +} + +type Backends []*SBackend + +type SBackend struct { + Index int + Weight int + Port int + ID string + BackendType string +} + +func NewBackend(s string, index int) (*SBackend, error) { + backend := &SBackend{Index: index} + for _, part := range strings.Split(s, ",") { + value := strings.Split(part, ":") + if len(value) != 2 { + return nil, fmt.Errorf("invalid input params %s eg: weight:80,port:443,id:01e9d393-d2b8-4d2e-85fb-023b83889070,backend_type:guest", part) + } + switch value[0] { + case "weight": + weight, err := strconv.Atoi(value[1]) + if err != nil { + return nil, fmt.Errorf("invalid weight %s error: %v", value[1], err) + } + if weight < 0 || weight > 256 { + return nil, fmt.Errorf("invalid weight range, only support 0 ~ 256") + } + backend.Weight = weight + case "port": + port, err := strconv.Atoi(value[1]) + if err != nil { + return nil, fmt.Errorf("invalid port %s error: %v", value[1], err) + } + if port < 1 || port > 65535 { + return nil, fmt.Errorf("invalid port range, only support 1 ~ 65535") + } + backend.Port = port + case "backend_type": + if utils.IsInStringArray(value[1], []string{models.LB_BACKEND_GUEST, models.LB_BACKEND_HOST}) { + return nil, fmt.Errorf("invalid backend type %s only support %s %s", value[1], models.LB_BACKEND_GUEST, models.LB_BACKEND_HOST) + } + backend.BackendType = value[1] + case "id": + backend.ID = value[1] + default: + return nil, fmt.Errorf("invalid input type %s", value[0]) + } + } + return backend, nil +} + +func NewBackends(ss []string) (Backends, error) { + backends := Backends{} + for index, s := range ss { + backend, err := NewBackend(s, index) + if err != nil { + return nil, err + } + backends = append(backends, backend) + } + return backends, nil +} + +func (opts *LoadbalancerBackendGroupCreateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := optionsStructToParams(opts) + if err != nil { + return nil, err + } + backends, err := NewBackends(opts.Backend) + if err != nil { + return nil, err + } + backendJSON := jsonutils.Marshal(backends) + params.Set("backends", backendJSON) + return params, nil } type LoadbalancerBackendGroupGetOptions struct { diff --git a/pkg/mcclient/options/loadbalancercertificates.go b/pkg/mcclient/options/loadbalancercertificates.go index b93dabedaa..a88a441952 100644 --- a/pkg/mcclient/options/loadbalancercertificates.go +++ b/pkg/mcclient/options/loadbalancercertificates.go @@ -36,8 +36,10 @@ func loadbalancerCertificateLoadFiles(cert, pkey string, allowEmpty bool) (*json type LoadbalancerCertificateCreateOptions struct { NAME string - Cert string `required:"true" json:"-" help:"path to certificate file"` - Pkey string `required:"true" json:"-" help:"path to private key file"` + Cert string `required:"true" json:"-" help:"path to certificate file"` + Pkey string `required:"true" json:"-" help:"path to private key file"` + Region string `json:"cloudregion"` + ManagerId string } func (opts *LoadbalancerCertificateCreateOptions) Params() (*jsonutils.JSONDict, error) { diff --git a/pkg/mcclient/options/loadbalancerlisteners.go b/pkg/mcclient/options/loadbalancerlisteners.go index 7f2ddd087e..567409e549 100644 --- a/pkg/mcclient/options/loadbalancerlisteners.go +++ b/pkg/mcclient/options/loadbalancerlisteners.go @@ -3,10 +3,11 @@ package options type LoadbalancerListenerCreateOptions struct { NAME string - Loadbalancer string `required:"true"` - ListenerType string `required:"true" choices:"tcp|udp|http|https"` - ListenerPort *int `required:"true"` - BackendGroup string + Loadbalancer string `required:"true"` + ListenerType string `required:"true" choices:"tcp|udp|http|https"` + ListenerPort *int `required:"true"` + BackendServerPort *int + BackendGroup string Scheduler string `required:"true" choices:"rr|wrr|wlc|sch|tch"` @@ -160,3 +161,7 @@ type LoadbalancerListenerActionStatusOptions struct { ID string Status string `choices:"enabled|disabled"` } + +type LoadbalancerListenerActionSyncStatusOptions struct { + ID string +} diff --git a/pkg/mcclient/options/loadbalancers.go b/pkg/mcclient/options/loadbalancers.go index e6ea70ca87..0e27c8cfb6 100644 --- a/pkg/mcclient/options/loadbalancers.go +++ b/pkg/mcclient/options/loadbalancers.go @@ -1,9 +1,14 @@ package options type LoadbalancerCreateOptions struct { - NAME string - Network string - Address string + NAME string + Network string + Address string + AddressType string `choices:"intranet|internet"` + LoadbalancerSpec string `choices:"slb.s1.small|slb.s2.small|slb.s2.medium|slb.s3.small|slb.s3.medium|slb.s3.large"` + ChargeType string `choices:"traffic|bandwidth"` + Zone string + ManagerId string } type LoadbalancerGetOptions struct { @@ -21,6 +26,10 @@ type LoadbalancerDeleteOptions struct { ID string } +type LoadbalancerPurgeOptions struct { + ID string +} + type LoadbalancerListOptions struct { BaseListOptions Zone string @@ -35,3 +44,7 @@ type LoadbalancerActionStatusOptions struct { ID string Status string `choices:"enabled|disabled"` } + +type LoadbalancerActionSyncStatusOptions struct { + ID string +} diff --git a/pkg/mcclient/options/schedule.go b/pkg/mcclient/options/schedule.go index d6b5967b44..599708a82e 100644 --- a/pkg/mcclient/options/schedule.go +++ b/pkg/mcclient/options/schedule.go @@ -3,7 +3,9 @@ package options import "yunion.io/x/jsonutils" type ScheduleOptions struct { + Region string `help:"Preferred region where virtual server should be created" json:"prefer_region"` Zone string `help:"Preferred zone where virtual server should be created" json:"prefer_zone"` + Wire string `help:"Preferred wire where virtual server should be created" json:"prefer_wire"` Host string `help:"Preferred host where virtual server should be created" json:"prefer_host"` Schedtag []string `help:"Schedule policy, key = aggregate name, value = require|exclude|prefer|avoid" metavar:""` Hypervisor string `help:"Hypervisor type" choices:"kvm|esxi|baremetal|container|aliyun|azure|qcloud|aws|huawei"` diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index ec4d589d6b..ba7b82c87e 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -18,11 +18,12 @@ type ServerListOptions struct { Gpu *bool `help:"Show gpu servers"` Secgroup string `help:"Secgroup ID or Name"` AdminSecgroup string `help:"AdminSecgroup ID or Name"` - Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws"` + Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws|huawei"` Region string `help:"Show servers in cloudregion"` WithEip *bool `help:"Show Servers with EIP"` WithoutEip *bool `help:"Show Servers without EIP"` OsType string `help:"OS Type" choices:"linux|windows|vmware"` + OrderByDisk string `help:"Order by disk" choices:"asc|desc"` ResourceType string `help:"Resource type" choices:"shared|prepaid|dedicated"` @@ -44,6 +45,11 @@ type ServerIdsOptions struct { ID []string `help:"ID of servers to operate" metavar:"SERVER" json:"-"` } +type ServerDeleteBackupOptions struct { + ID string `help:"ID of the server" json:"-"` + Purge bool `help:"Purge Guest Backup" json:"purge"` +} + type ServerShowOptions struct { ID string `help:"ID or name of the server" json:"-"` WithMeta *bool `help:"With meta data"` @@ -104,7 +110,7 @@ type ServerCreateOptions struct { Keypair string `help:"SSH Keypair"` Password string `help:"Default user password"` Iso string `help:"ISO image ID" metavar:"IMAGE_ID" json:"cdrom"` - Ncpu *int `help:"#CPU cores of VM server, default 1" default:"1" metavar:"" json:"vcpu_count"` + VcpuCount *int `help:"#CPU cores of VM server, default 1" default:"1" metavar:"" json:"vcpu_count" token:"ncpu"` Vga string `help:"VGA driver" choices:"std|vmware|cirrus|qxl"` Vdi string `help:"VDI protocool" choices:"vnc|spice"` Bios string `help:"BIOS" choices:"BIOS|UEFI"` @@ -129,9 +135,13 @@ type ServerCreateOptions struct { Duration string `help:"valid duration of the server, e.g. 1H, 1D, 1W, 1M, 1Y, ADMIN ONLY option"` - AutoPrepaidRecycle bool `help:"automatically enable prepaid recycling after server is created successfully" json:",omitfalse"` + AutoPrepaidRecycle bool `help:"automatically enable prepaid recycling after server is created successfully" json:"auto_prepaid_recycle,omitfalse"` GenerateName bool `help:"name is generated by pattern" json:"-"` + + EipBw int `help:"allocate EIP with bandwidth in MB when server is created" json:"eip_bw,omitzero"` + EipChargeType string `help:"newly allocated EIP charge type, either traffic or bandwidth" choices:"traffic|bandwidth" json:"eip_charge_type,omitempty"` + Eip string `help:"associate with an existing EIP when server is created" json:"eip,omitempty"` } func (opts *ServerCreateOptions) Params() (*jsonutils.JSONDict, error) { @@ -295,7 +305,7 @@ type ServerSaveImageOptions struct { type ServerRebuildRootOptions struct { ID string `help:"Server to rebuild root" json:"-"` - Image string `help:"New root Image template ID" json:"image_id"` + ImageId string `help:"New root Image template ID" json:"image_id" token:"image"` Keypair string `help:"ssh Keypair used for login"` Password string `help:"Default user password"` NoAccountInit *bool `help:"Not reset account password"` @@ -304,10 +314,10 @@ type ServerRebuildRootOptions struct { } type ServerChangeConfigOptions struct { - ID string `help:"Server to rebuild root" json:"-"` - Ncpu *int `help:"New number of Virtual CPU cores" json:"vcpu_count"` - Vmem string `help:"New memory size" json:"vmem_size"` - Disk []string `help:"Data disk description, from the 1st data disk to the last one, empty string if no change for this data disk"` + ID string `help:"Server to rebuild root" json:"-"` + VcpuCount *int `help:"New number of Virtual CPU cores" json:"vcpu_count" token:"ncpu"` + VmemSize string `help:"New memory size" json:"vmem_size" token:"vmem"` + Disk []string `help:"Data disk description, from the 1st data disk to the last one, empty string if no change for this data disk"` InstanceType string `help:"Instance Type, e.g. S2.SMALL2 for qcloud"` } diff --git a/pkg/mcclient/session.go b/pkg/mcclient/session.go index fde602d14d..4f09e8b03d 100644 --- a/pkg/mcclient/session.go +++ b/pkg/mcclient/session.go @@ -92,6 +92,10 @@ func SplitVersionedURL(url string) (string, string) { return base }*/ +func (this *ClientSession) GetEndpointType() string { + return this.endpointType +} + func (this *ClientSession) GetClient() *Client { return this.client } diff --git a/pkg/mcclient/token3.go b/pkg/mcclient/token3.go index 38e962b119..da9b4e0bae 100644 --- a/pkg/mcclient/token3.go +++ b/pkg/mcclient/token3.go @@ -13,19 +13,19 @@ import ( const REGION_ZONE_SEP = '-' type KeystoneEndpointV3 struct { - Id string - Interface string - Region string - Region_id string - Url string - Name string + Id string `json:"id"` + Interface string `json:"interface"` + Region string `json:"region"` + Region_id string `json:"region_id"` + Url string `json:"url"` + Name string `json:"name"` } type KeystoneServiceV3 struct { - Id string - Name string - Type string - Endpoints []KeystoneEndpointV3 + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Endpoints []KeystoneEndpointV3 `json:"endpoint"` } type KeystoneDomainV3 struct { diff --git a/pkg/scheduler/api/sched.go b/pkg/scheduler/api/sched.go index c2cf22f502..49708d26de 100644 --- a/pkg/scheduler/api/sched.go +++ b/pkg/scheduler/api/sched.go @@ -401,9 +401,6 @@ func (d *SchedData) fillNetworksInfo(sjson *simplejson.Json) error { if err != nil || net.Idx == "" { net = new(Network) net.Idx = s.Get("network").MustString() - if net.Idx == "" { - return fmt.Errorf("Invalid network desc: %s", s.MustString()) - } net.Wire = s.Get("wire").MustString() net.Driver = s.Get("driver").MustString() net.Exit = s.Get("exit").MustBool() @@ -613,11 +610,17 @@ func newBaremetalDiskConfigFromSimpleJson(sjson *simplejson.Json) (*baremetal.Ba baremetalDiskConfig.Range = rangeArray baremetalDiskConfig.Splits = sjson.Get("splits").MustString() - baremetalDiskConfig.Strip = sjson.Get("strip").MustInt64() baremetalDiskConfig.Type = sjson.Get("type").MustString() ada := sjson.Get("adapter").MustInt() baremetalDiskConfig.Adapter = &ada - baremetalDiskConfig.Cachedbadbbu = sjson.Get("cachedbadbbu").MustBool() + if val, ok := sjson.CheckGet("strip"); ok { + strip := val.MustInt64() + baremetalDiskConfig.Strip = &strip + } + if val, ok := sjson.CheckGet("cachedbadbbu"); ok { + cachedbadbbu := val.MustBool() + baremetalDiskConfig.Cachedbadbbu = &cachedbadbbu + } return baremetalDiskConfig, nil } diff --git a/pkg/util/aliyun/aliyun.go b/pkg/util/aliyun/aliyun.go index becd3bc054..830422825c 100644 --- a/pkg/util/aliyun/aliyun.go +++ b/pkg/util/aliyun/aliyun.go @@ -37,10 +37,12 @@ type SAliyunClient struct { accessKey string secret string iregions []cloudprovider.ICloudRegion + + Debug bool } -func NewAliyunClient(providerId string, providerName string, accessKey string, secret string) (*SAliyunClient, error) { - client := SAliyunClient{providerId: providerId, providerName: providerName, accessKey: accessKey, secret: secret} +func NewAliyunClient(providerId string, providerName string, accessKey string, secret string, isDebug bool) (*SAliyunClient, error) { + client := SAliyunClient{providerId: providerId, providerName: providerName, accessKey: accessKey, secret: secret, Debug: isDebug} err := client.fetchRegions() if err != nil { return nil, err @@ -53,7 +55,12 @@ func jsonRequest(client *sdk.Client, domain, apiVersion, apiName string, params resp, err := _jsonRequest(client, domain, apiVersion, apiName, params) retry := false if err != nil { - for _, code := range []string{"SignatureNonceUsed", "InvalidInstance.NotSupported"} { + for _, code := range []string{"404 Not Found"} { + if strings.Contains(err.Error(), code) { + return nil, cloudprovider.ErrNotFound + } + } + for _, code := range []string{"SignatureNonceUsed", "InvalidInstance.NotSupported", "try later", "BackendServer.configuring"} { if strings.Contains(err.Error(), code) { retry = true break diff --git a/pkg/util/aliyun/eip.go b/pkg/util/aliyun/eip.go index 7c77a4f1b3..3f32e82b4f 100644 --- a/pkg/util/aliyun/eip.go +++ b/pkg/util/aliyun/eip.go @@ -242,7 +242,7 @@ func (region *SRegion) AllocateEIP(bwMbps int, chargeType TInternetChargeType) ( return region.GetEip(eipId) } -func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) { +func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) { var ctype TInternetChargeType switch chargeType { case models.EIP_CHARGE_TYPE_BY_TRAFFIC: diff --git a/pkg/util/aliyun/host.go b/pkg/util/aliyun/host.go index 0702b96e77..488ca19655 100644 --- a/pkg/util/aliyun/host.go +++ b/pkg/util/aliyun/host.go @@ -166,11 +166,8 @@ func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) { return inst, nil } -func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, - vswitchId string, ipAddr string, desc string, passwd string, - storageType string, diskSizes []int, publicKey string, secgroupId string, userData string, - bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - vmId, err := self._createVM(name, imgId, sysDiskSize, cpu, memMB, "", vswitchId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, secgroupId, userData, bc) +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + vmId, err := self._createVM(desc.Name, desc.ExternalImageId, desc.SysDisk, desc.Cpu, desc.MemoryMB, desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, desc.DataDisks, desc.PublicKey, desc.ExternalSecgroupId, desc.UserData, desc.BillingCycle) if err != nil { return nil, err } @@ -182,25 +179,9 @@ func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, return vm, err } -func (self *SHost) CreateVM2(name string, imgId string, sysDiskSize int, instanceType string, +func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SDiskInfo, cpu int, memMB int, instanceType string, vswitchId string, ipAddr string, desc string, passwd string, - storageType string, diskSizes []int, publicKey string, secgroupId string, - userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - vmId, err := self._createVM(name, imgId, sysDiskSize, 0, 0, instanceType, vswitchId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, secgroupId, userData, bc) - if err != nil { - return nil, err - } - vm, err := self.GetInstanceById(vmId) - if err != nil { - return nil, err - } - // err = vm.waitStatus(InstanceStatusStopped, time.Second*10, time.Second*1800) - return vm, err -} - -func (self *SHost) _createVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, instanceType string, - vswitchId string, ipAddr string, desc string, passwd string, - storageType string, diskSizes []int, publicKey string, secgroupId string, + dataDisks []cloudprovider.SDiskInfo, publicKey string, secgroupId string, userData string, bc *billing.SBillingCycle) (string, error) { net := self.zone.getNetworkById(vswitchId) if net == nil { @@ -226,7 +207,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDiskSize int, cpu int img, err := self.zone.region.GetImage(imgId) if err != nil { - log.Errorf("getiamge fail %s", err) + log.Errorf("GetImage fail %s", err) return "", err } if img.Status != ImageStatusAvailable { @@ -234,21 +215,24 @@ func (self *SHost) _createVM(name string, imgId string, sysDiskSize int, cpu int return "", fmt.Errorf("image not ready") } - _, err = self.zone.getStorageByCategory(storageType) - if err != nil { - return "", fmt.Errorf("Storage %s not avaiable: %s", storageType, err) - } - - disks := make([]SDisk, len(diskSizes)+1) + disks := make([]SDisk, len(dataDisks)+1) disks[0].Size = img.Size - if sysDiskSize > 0 && sysDiskSize > img.Size { - disks[0].Size = sysDiskSize + if sysDisk.SizeGB > 0 && sysDisk.SizeGB > img.Size { + disks[0].Size = sysDisk.SizeGB } - disks[0].Category = storageType + storage, err := self.zone.getStorageByCategory(sysDisk.StorageType) + if err != nil { + return "", fmt.Errorf("Storage %s not avaiable: %s", sysDisk.StorageType, err) + } + disks[0].Category = storage.storageType - for i, sz := range diskSizes { - disks[i+1].Size = sz - disks[i+1].Category = storageType + for i, dataDisk := range dataDisks { + disks[i+1].Size = dataDisk.SizeGB + storage, err := self.zone.getStorageByCategory(dataDisk.StorageType) + if err != nil { + return "", fmt.Errorf("Storage %s not avaiable: %s", dataDisk.StorageType, err) + } + disks[i+1].Category = storage.storageType } if len(instanceType) > 0 { diff --git a/pkg/util/aliyun/image.go b/pkg/util/aliyun/image.go index 44b270ff5a..561cf58831 100644 --- a/pkg/util/aliyun/image.go +++ b/pkg/util/aliyun/image.go @@ -13,6 +13,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/pkg/utils" ) type ImageStatusType string @@ -65,7 +66,7 @@ func (self *SImage) GetMetadata() *jsonutils.JSONDict { data.Add(jsonutils.NewString(self.Architecture), "os_arch") } if len(self.OSType) > 0 { - data.Add(jsonutils.NewString(self.OSType), "os_name") + data.Add(jsonutils.NewString(self.GetOsType()), "os_name") } if len(self.Platform) > 0 { data.Add(jsonutils.NewString(self.Platform), "os_distribution") @@ -81,7 +82,11 @@ func (self *SImage) GetId() string { } func (self *SImage) GetName() string { - return self.ImageName + if self.ImageOwnerAlias == ImageOwnerSystem { + return self.OSName + } else { + return self.ImageName + } } func (self *SImage) IsEmulated() bool { @@ -93,7 +98,7 @@ func (self *SImage) Delete(ctx context.Context) error { } func (self *SImage) GetGlobalId() string { - panic("not implemented") + return self.ImageId } func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { @@ -103,15 +108,30 @@ func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { func (self *SImage) GetStatus() string { switch self.Status { case ImageStatusCreating: - return models.IMAGE_STATUS_QUEUED + return models.CACHED_IMAGE_STATUS_SAVING case ImageStatusAvailable: - return models.IMAGE_STATUS_ACTIVE + return models.CACHED_IMAGE_STATUS_READY case ImageStatusUnAvailable: - return models.IMAGE_STATUS_DELETED + return models.CACHED_IMAGE_STATUS_CACHE_FAILED case ImageStatusCreateFailed: - return models.IMAGE_STATUS_KILLED + return models.CACHED_IMAGE_STATUS_CACHE_FAILED default: - return models.IMAGE_STATUS_KILLED + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (self *SImage) GetImageStatus() string { + switch self.Status { + case ImageStatusCreating: + return cloudprovider.IMAGE_STATUS_QUEUED + case ImageStatusAvailable: + return cloudprovider.IMAGE_STATUS_ACTIVE + case ImageStatusUnAvailable: + return cloudprovider.IMAGE_STATUS_DELETED + case ImageStatusCreateFailed: + return cloudprovider.IMAGE_STATUS_KILLED + default: + return cloudprovider.IMAGE_STATUS_KILLED } } @@ -123,6 +143,53 @@ func (self *SImage) Refresh() error { return jsonutils.Update(self, new) } +func (self *SImage) GetImageType() string { + switch self.ImageOwnerAlias { + case ImageOwnerSystem: + return cloudprovider.CachedImageTypeSystem + case ImageOwnerSelf: + return cloudprovider.CachedImageTypeCustomized + case ImageOwnerMarketplace: + return cloudprovider.CachedImageTypeMarket + case ImageOwnerOthers: + return cloudprovider.CachedImageTypeShared + default: + return cloudprovider.CachedImageTypeCustomized + } +} + +func (self *SImage) GetSize() int64 { + return int64(self.Size) * 1024 * 1024 * 1024 +} + +func (self *SImage) GetOsType() string { + return utils.Capitalize(self.OSType) +} + +func (self *SImage) GetOsDist() string { + return self.Platform +} + +func (self *SImage) GetOsVersion() string { + return "" +} + +func (self *SImage) GetOsArch() string { + return self.Architecture +} + +func (self *SImage) GetMinOsDiskSizeGb() int { + return 40 +} + +func (self *SImage) GetImageFormat() string { + return "vhd" +} + +func (self *SImage) GetCreateTime() time.Time { + return self.CreationTime +} + type ImageExportTask struct { ImageId string RegionId string @@ -188,7 +255,7 @@ func (self *SRegion) ImportImage(name string, osArch string, osType string, osDi } func (self *SRegion) GetImage(imageId string) (*SImage, error) { - images, _, err := self.GetImages("", ImageOwnerSelf, []string{imageId}, "", 0, 1) + images, _, err := self.GetImages("", "", []string{imageId}, "", 0, 1) if err != nil { return nil, err } @@ -199,7 +266,7 @@ func (self *SRegion) GetImage(imageId string) (*SImage, error) { } func (self *SRegion) GetImageByName(name string) (*SImage, error) { - images, _, err := self.GetImages("", ImageOwnerSelf, nil, name, 0, 1) + images, _, err := self.GetImages("", "", nil, name, 0, 1) if err != nil { return nil, err } diff --git a/pkg/util/aliyun/loadbalancer.go b/pkg/util/aliyun/loadbalancer.go index 1000d723ca..b1a0b974bd 100644 --- a/pkg/util/aliyun/loadbalancer.go +++ b/pkg/util/aliyun/loadbalancer.go @@ -5,6 +5,7 @@ import ( "strings" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" ) @@ -58,7 +59,8 @@ type SLoadbalancer struct { InternetChargeType string //公网实例的计费方式。取值:paybybandwidth:按带宽计费 paybytraffic:按流量计费(默认值) 说明 当 PayType参数的值为PrePay时,只支持按带宽计费。 PayType string //实例的计费类型,取值:PayOnDemand:按量付费 PrePay:预付费 ResourceGroupId string //企业资源组ID。 - + LoadBalancerSpec string //负载均衡实例的的性能规格 + Bandwidth int //按带宽计费的公网型实例的带宽峰值 } func (lb *SLoadbalancer) GetName() string { @@ -97,11 +99,16 @@ func (lb *SLoadbalancer) GetNetworkType() string { } func (lb *SLoadbalancer) GetNetworkId() string { - return "" + return lb.VSwitchId } func (lb *SLoadbalancer) GetZoneId() string { - return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_ALIYUN, lb.MasterZoneId) + zone, err := lb.region.getZoneById(lb.MasterZoneId) + if err != nil { + log.Errorf("failed to find zone for lb %s error: %v", lb.LoadBalancerName, err) + return "" + } + return zone.GetGlobalId() } func (lb *SLoadbalancer) IsEmulated() bool { @@ -142,11 +149,19 @@ func (region *SRegion) GetLoadbalancerDetail(loadbalancerId string) (*SLoadbalan if err != nil { return nil, err } - lb := SLoadbalancer{} + lb := SLoadbalancer{region: region} return &lb, body.Unmarshal(&lb) } -func (lb *SLoadbalancer) GetILoadbalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { +func (lb *SLoadbalancer) Delete() error { + params := map[string]string{} + params["RegionId"] = lb.region.RegionId + params["LoadBalancerId"] = lb.LoadBalancerId + _, err := lb.region.lbRequest("DeleteLoadBalancer", params) + return err +} + +func (lb *SLoadbalancer) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { ibackendgroups := []cloudprovider.ICloudLoadbalancerBackendGroup{} { backendgroups, err := lb.region.GetLoadbalancerBackendgroups(lb.LoadBalancerId) @@ -177,7 +192,108 @@ func (lb *SLoadbalancer) GetILoadbalancerBackendGroups() ([]cloudprovider.ICloud return ibackendgroups, nil } -func (lb *SLoadbalancer) GetILoadbalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { +func (lb *SLoadbalancer) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + switch group.GroupType { + case models.LB_BACKENDGROUP_TYPE_NORMAL: + group, err := lb.region.CreateLoadbalancerBackendGroup(group.Name, lb.LoadBalancerId, group.Backends) + if err != nil { + return nil, err + } + group.lb = lb + return group, nil + case models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE: + group, err := lb.region.CreateLoadbalancerMasterSlaveBackendGroup(group.Name, lb.LoadBalancerId, group.Backends) + if err != nil { + return nil, err + } + group.lb = lb + return group, nil + default: + return nil, fmt.Errorf("Unsupport backendgroup type %s", group.GroupType) + } +} + +func (lb *SLoadbalancer) CreateILoadBalancerListener(listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + switch listener.ListenerType { + case models.LB_LISTENER_TYPE_TCP: + return lb.region.CreateLoadbalancerTCPListener(lb, listener) + case models.LB_LISTENER_TYPE_UDP: + return lb.region.CreateLoadbalancerUDPListener(lb, listener) + case models.LB_LISTENER_TYPE_HTTP: + return lb.region.CreateLoadbalancerHTTPListener(lb, listener) + case models.LB_LISTENER_TYPE_HTTPS: + return lb.region.CreateLoadbalancerHTTPSListener(lb, listener) + } + return nil, fmt.Errorf("unsupport listener type %s", listener.ListenerType) +} + +func (lb *SLoadbalancer) GetLoadbalancerSpec() string { + if len(lb.LoadBalancerSpec) == 0 { + lb.Refresh() + } + return lb.LoadBalancerSpec +} + +func (lb *SLoadbalancer) GetChargeType() string { + switch lb.InternetChargeType { + case "paybybandwidth": + return "bandwidth" + case "paybytraffic": + return "traffic" + } + return "unknown" +} + +func (lb *SLoadbalancer) GetILoadBalancerBackendGroupById(groupId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + groups, err := lb.GetILoadBalancerBackendGroups() + if err != nil { + return nil, err + } + for i := 0; i < len(groups); i++ { + if groups[i].GetGlobalId() == groupId { + return groups[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) loadbalancerOperation(loadbalancerId, status string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["LoadBalancerStatus"] = status + _, err := region.lbRequest("SetLoadBalancerStatus", params) + return err +} + +func (lb *SLoadbalancer) Start() error { + if lb.LoadBalancerStatus != "active" { + return lb.region.loadbalancerOperation(lb.LoadBalancerId, "active") + } + return nil +} + +func (lb *SLoadbalancer) Stop() error { + if lb.LoadBalancerStatus != "inactive" { + return lb.region.loadbalancerOperation(lb.LoadBalancerId, "inactive") + } + return nil +} + +func (lb *SLoadbalancer) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { + listener, err := lb.GetILoadBalancerListeners() + if err != nil { + return nil, err + } + for i := 0; i < len(listener); i++ { + if listener[i].GetGlobalId() == listenerId { + return listener[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (lb *SLoadbalancer) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { loadbalancer, err := lb.region.GetLoadbalancerDetail(lb.LoadBalancerId) if err != nil { return nil, err diff --git a/pkg/util/aliyun/loadbalanceracl.go b/pkg/util/aliyun/loadbalanceracl.go index 37a6cf0a0d..226040a686 100644 --- a/pkg/util/aliyun/loadbalanceracl.go +++ b/pkg/util/aliyun/loadbalanceracl.go @@ -3,6 +3,7 @@ package aliyun import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudprovider" ) type AclEntrys struct { @@ -55,17 +56,43 @@ func (acl *SLoadbalancerAcl) Refresh() error { return jsonutils.Update(acl, loadbalancerAcl) } -func (acl *SLoadbalancerAcl) GetAclEntries() *jsonutils.JSONArray { - result := jsonutils.NewArray() +func (acl *SLoadbalancerAcl) GetAclEntries() []cloudprovider.SLoadbalancerAccessControlListEntry { detail, err := acl.region.GetLoadbalancerAclDetail(acl.AclId) if err != nil { log.Errorf("GetLoadbalancerAclDetail %s failed: %v", acl.AclId, err) - return result + return nil } + entrys := []cloudprovider.SLoadbalancerAccessControlListEntry{} for _, entry := range detail.AclEntrys.AclEntry { - result.Add(jsonutils.Marshal(map[string]string{"cidr": entry.AclEntryIP, "comment": entry.AclEntryComment})) + entrys = append(entrys, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: entry.AclEntryIP, Comment: entry.AclEntryComment}) } - return result + return entrys +} + +func (region *SRegion) UpdateAclName(aclId, name string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + params["AclName"] = name + _, err := region.lbRequest("SetAccessControlListAttribute", params) + return err +} + +func (region *SRegion) RemoveAccessControlListEntry(aclId string, data jsonutils.JSONObject) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + params["AclEntrys"] = data.String() + _, err := region.lbRequest("RemoveAccessControlListEntry", params) + return err +} + +func (acl *SLoadbalancerAcl) Delete() error { + params := map[string]string{} + params["RegionId"] = acl.region.RegionId + params["AclId"] = acl.AclId + _, err := acl.region.lbRequest("DeleteAccessControlList", params) + return err } func (region *SRegion) GetLoadbalancerAclDetail(aclId string) (*SLoadbalancerAcl, error) { @@ -76,11 +103,11 @@ func (region *SRegion) GetLoadbalancerAclDetail(aclId string) (*SLoadbalancerAcl if err != nil { return nil, err } - detail := SLoadbalancerAcl{} + detail := SLoadbalancerAcl{region: region} return &detail, body.Unmarshal(&detail) } -func (region *SRegion) GetLoadbalancerAcls() ([]SLoadbalancerAcl, error) { +func (region *SRegion) GetLoadBalancerAcls() ([]SLoadbalancerAcl, error) { params := map[string]string{} params["RegionId"] = region.RegionId body, err := region.lbRequest("DescribeAccessControlLists", params) @@ -90,3 +117,24 @@ func (region *SRegion) GetLoadbalancerAcls() ([]SLoadbalancerAcl, error) { acls := []SLoadbalancerAcl{} return acls, body.Unmarshal(&acls, "Acls", "Acl") } + +func (acl *SLoadbalancerAcl) Sync(_acl *cloudprovider.SLoadbalancerAccessControlList) error { + if acl.AclName != _acl.Name { + if err := acl.region.UpdateAclName(acl.AclId, _acl.Name); err != nil { + return err + } + } + entrys := jsonutils.NewArray() + for _, entry := range acl.AclEntrys.AclEntry { + entrys.Add(jsonutils.Marshal(map[string]string{"entry": entry.AclEntryIP, "comment": entry.AclEntryComment})) + } + if entrys.Length() > 0 { + if err := acl.region.RemoveAccessControlListEntry(acl.AclId, entrys); err != nil && !isError(err, "Acl does not have any entry") { + return err + } + } + if len(_acl.Entrys) > 0 { + return acl.region.AddAccessControlListEntry(acl.AclId, _acl.Entrys) + } + return nil +} diff --git a/pkg/util/aliyun/loadbalancerbackendgroup.go b/pkg/util/aliyun/loadbalancerbackendgroup.go index 97e9948778..ed928904bf 100644 --- a/pkg/util/aliyun/loadbalancerbackendgroup.go +++ b/pkg/util/aliyun/loadbalancerbackendgroup.go @@ -1,8 +1,11 @@ package aliyun import ( + "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type Rule struct { @@ -51,15 +54,15 @@ func (backendgroup *SLoadbalancerBackendGroup) GetGlobalId() string { } func (backendgroup *SLoadbalancerBackendGroup) GetStatus() string { - return "" + return models.LB_STATUS_ENABLED } func (backendgroup *SLoadbalancerBackendGroup) IsDefault() bool { - return true + return false } func (backendgroup *SLoadbalancerBackendGroup) GetType() string { - return "standard" + return models.LB_BACKENDGROUP_TYPE_NORMAL } func (backendgroup *SLoadbalancerBackendGroup) GetMetadata() *jsonutils.JSONDict { @@ -107,3 +110,108 @@ func (backendgroup *SLoadbalancerBackendGroup) GetILoadbalancerBackends() ([]clo } return ibackends, nil } + +func (region *SRegion) CreateLoadbalancerBackendGroup(name, loadbalancerId string, backends []cloudprovider.SLoadbalancerBackend) (*SLoadbalancerBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupName"] = name + params["LoadBalancerId"] = loadbalancerId + if len(backends) > 0 { + servers := jsonutils.NewArray() + for _, backend := range backends { + servers.Add( + jsonutils.Marshal( + map[string]string{ + "ServerId": backend.ExternalID, + "Port": fmt.Sprintf("%d", backend.Port), + "Weight": fmt.Sprintf("%d", backend.Weight), + }, + )) + } + params["BackendServers"] = servers.String() + } + body, err := region.lbRequest("CreateVServerGroup", params) + if err != nil { + return nil, err + } + groupId, err := body.GetString("VServerGroupId") + if err != nil { + return nil, err + } + return region.GetLoadbalancerBackendgroupById(groupId) +} + +func (region *SRegion) GetLoadbalancerBackendgroupById(groupId string) (*SLoadbalancerBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = groupId + body, err := region.lbRequest("DescribeVServerGroupAttribute", params) + if err != nil { + return nil, err + } + group := &SLoadbalancerBackendGroup{} + return group, body.Unmarshal(group) +} + +func (region *SRegion) UpdateLoadBalancerBackendGroupName(name, groupId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = groupId + params["VServerGroupName"] = name + _, err := region.lbRequest("SetVServerGroupAttribute", params) + return err +} + +func (backendgroup *SLoadbalancerBackendGroup) Sync(name string) error { + if backendgroup.VServerGroupName != name { + return backendgroup.lb.region.UpdateLoadBalancerBackendGroupName(backendgroup.VServerGroupId, name) + } + return nil +} + +func (region *SRegion) DeleteLoadBalancerBackendGroup(groupId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["VServerGroupId"] = groupId + _, err := region.lbRequest("DeleteVServerGroup", params) + return err +} + +func (backendgroup *SLoadbalancerBackendGroup) Delete() error { + return backendgroup.lb.region.DeleteLoadBalancerBackendGroup(backendgroup.VServerGroupId) +} + +func (region *SRegion) AddBackendVServer(loadbalancerId, backendGroupId, serverId string, weight, port int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["VServerGroupId"] = backendGroupId + servers := jsonutils.NewArray() + servers.Add(jsonutils.Marshal(map[string]string{"ServerId": serverId, "Weight": fmt.Sprintf("%d", weight), "Port": fmt.Sprintf("%d", port)})) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("AddVServerGroupBackendServers", params) + return err +} + +func (region *SRegion) RemoveBackendVServer(loadbalancerId, backendgroupId, serverId string, port int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["VServerGroupId"] = backendgroupId + servers := jsonutils.NewArray() + servers.Add(jsonutils.Marshal(map[string]string{"ServerId": serverId, "Port": fmt.Sprintf("%d", port)})) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("RemoveVServerGroupBackendServers", params) + return err +} + +func (backendgroup *SLoadbalancerBackendGroup) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + if err := backendgroup.lb.region.AddBackendVServer(backendgroup.lb.LoadBalancerId, backendgroup.VServerGroupId, serverId, weight, port); err != nil { + return nil, err + } + return &SLoadbalancerBackend{lbbg: backendgroup, ServerId: serverId, Weight: weight, Port: port}, nil +} + +func (backendgroup *SLoadbalancerBackendGroup) RemoveBackendServer(serverId string, weight, port int) error { + return backendgroup.lb.region.RemoveBackendVServer(backendgroup.lb.LoadBalancerId, backendgroup.VServerGroupId, serverId, port) +} diff --git a/pkg/util/aliyun/loadbalancerdefaultbackendgroup.go b/pkg/util/aliyun/loadbalancerdefaultbackendgroup.go index 29ffa14d92..793e529525 100644 --- a/pkg/util/aliyun/loadbalancerdefaultbackendgroup.go +++ b/pkg/util/aliyun/loadbalancerdefaultbackendgroup.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type SLoadbalancerDefaultBackendGroup struct { @@ -24,15 +25,15 @@ func (backendgroup *SLoadbalancerDefaultBackendGroup) GetGlobalId() string { } func (backendgroup *SLoadbalancerDefaultBackendGroup) GetStatus() string { - return "" + return models.LB_STATUS_ENABLED } func (backendgroup *SLoadbalancerDefaultBackendGroup) IsDefault() bool { - return false + return true } func (backendgroup *SLoadbalancerDefaultBackendGroup) GetType() string { - return "default" + return models.LB_BACKENDGROUP_TYPE_DEFAULT } func (backendgroup *SLoadbalancerDefaultBackendGroup) GetMetadata() *jsonutils.JSONDict { @@ -59,3 +60,44 @@ func (backendgroup *SLoadbalancerDefaultBackendGroup) GetILoadbalancerBackends() } return ibackends, nil } + +func (backendgroup *SLoadbalancerDefaultBackendGroup) Sync(name string) error { + return cloudprovider.ErrNotSupported +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) Delete() error { + return cloudprovider.ErrNotSupported +} + +func (region *SRegion) AddBackendServer(loadbalancerId, serverId string, weight, port int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + servers := jsonutils.NewArray() + servers.Add(jsonutils.Marshal(map[string]string{"ServerId": serverId, "Weight": fmt.Sprintf("%d", weight)})) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("AddBackendServers", params) + return err +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + if err := backendgroup.lb.region.AddBackendServer(backendgroup.lb.LoadBalancerId, serverId, weight, port); err != nil { + return nil, err + } + return &SLoadbalancerDefaultBackend{lbbg: backendgroup, ServerId: serverId, Weight: weight}, nil +} + +func (region *SRegion) RemoveBackendServer(loadbalancerId, serverId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + servers := jsonutils.NewArray() + servers.Add(jsonutils.NewString(serverId)) + params["BackendServers"] = servers.String() + _, err := region.lbRequest("RemoveBackendServers", params) + return err +} + +func (backendgroup *SLoadbalancerDefaultBackendGroup) RemoveBackendServer(serverId string, weight, port int) error { + return backendgroup.lb.region.RemoveBackendServer(backendgroup.lb.LoadBalancerId, serverId) +} diff --git a/pkg/util/aliyun/loadbalancerhttplistener.go b/pkg/util/aliyun/loadbalancerhttplistener.go index 05ebd49ed9..56cefc8783 100644 --- a/pkg/util/aliyun/loadbalancerhttplistener.go +++ b/pkg/util/aliyun/loadbalancerhttplistener.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type SLoadbalancerHTTPListener struct { @@ -14,6 +15,7 @@ type SLoadbalancerHTTPListener struct { BackendServerPort int // 负载均衡实例后端使用的端口。 Bandwidth int // 监听的带宽峰值。 Status string // 当前监听的状态。取值:starting | running | configuring | stopping | stopped + Description string XForwardedFor string // 是否开启通过X-Forwarded-For头字段获取访者真实IP。 XForwardedFor_SLBIP string // 是否通过SLB-IP头字段获取客户端请求的真实IP。 @@ -52,9 +54,16 @@ type SLoadbalancerHTTPListener struct { Rules Rules //监听下的转发规则列表,具体请参见RuleList。 ForwardPort int // HTTP至HTTPS的监听转发端口。暂时只支持将HTTP 80访问重定向转发至HTTPS 443。 说明 如果 ListenerForward的值为 off,该参数不显示。 ListenerForward string // 表示是否开启HTTP至HTTPS的监听转发。on:表示开启 off:表示未开启 + VServerGroupId string // 绑定的服务器组ID } func (listener *SLoadbalancerHTTPListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } return fmt.Sprintf("HTTP:%d", listener.ListenerPort) } @@ -67,7 +76,14 @@ func (listerner *SLoadbalancerHTTPListener) GetGlobalId() string { } func (listerner *SLoadbalancerHTTPListener) GetStatus() string { - return listerner.Status + switch listerner.Status { + case "starting", "running": + return models.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return models.LB_STATUS_DISABLED + default: + return models.LB_STATUS_UNKNOWN + } } func (listerner *SLoadbalancerHTTPListener) GetMetadata() *jsonutils.JSONDict { @@ -79,7 +95,11 @@ func (listerner *SLoadbalancerHTTPListener) IsEmulated() bool { } func (listerner *SLoadbalancerHTTPListener) Refresh() error { - return nil + lis, err := listerner.lb.region.GetLoadbalancerHTTPListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) } func (listerner *SLoadbalancerHTTPListener) GetListenerType() string { @@ -91,7 +111,14 @@ func (listerner *SLoadbalancerHTTPListener) GetListenerPort() int { } func (listerner *SLoadbalancerHTTPListener) GetBackendGroupId() string { - return "" + if len(listerner.VServerGroupId) == 0 { + listerner.Refresh() + } + return listerner.VServerGroupId +} + +func (listerner *SLoadbalancerHTTPListener) GetBackendServerPort() int { + return listerner.BackendServerPort } func (listerner *SLoadbalancerHTTPListener) GetScheduler() string { @@ -224,3 +251,94 @@ func (region *SRegion) GetLoadbalancerHTTPListener(loadbalancerId string, listen listener := SLoadbalancerHTTPListener{} return &listener, body.Unmarshal(&listener) } + +func (region *SRegion) DeleteLoadbalancerListener(loadbalancerId string, listenerPort int) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + _, err := region.lbRequest("DeleteLoadBalancerListener", params) + return err +} + +func (region *SRegion) CreateLoadbalancerHTTPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + _, err := region.lbRequest("CreateLoadBalancerHTTPListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerHTTPListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerHTTPListener) Delete() error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + _rule := &SLoadbalancerListenerRule{ + Domain: rule.Domain, + Url: rule.Path, + RuleName: rule.Name, + } + if len(rule.BackendGroupID) > 0 { //&& rule.BackendGroupType == models.LB_BACKENDGROUP_TYPE_NORMAL { + _rule.VServerGroupId = rule.BackendGroupID + } + listenerRule, err := listerner.lb.region.CreateLoadbalancerListenerRule(listerner.ListenerPort, listerner.lb.LoadBalancerId, _rule) + if err != nil { + return nil, err + } + listenerRule.httpListener = listerner + return listenerRule, nil +} + +func (listerner *SLoadbalancerHTTPListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + rule, err := listerner.lb.region.GetLoadbalancerListenerRule(ruleId) + if err != nil { + return nil, err + } + rule.httpListener = listerner + return rule, nil +} + +func (region *SRegion) startListener(listenerPort int, loadbalancerId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + _, err := region.lbRequest("StartLoadBalancerListener", params) + return err +} + +func (region *SRegion) stopListener(listenerPort int, loadbalancerId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerId"] = loadbalancerId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + _, err := region.lbRequest("StopLoadBalancerListener", params) + return err +} + +func (listerner *SLoadbalancerHTTPListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerHTTPListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerHTTPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + _, err := region.lbRequest("SetLoadBalancerHTTPListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerHTTPListener) Sync(lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerHTTPListener(listerner.lb, lblis) +} diff --git a/pkg/util/aliyun/loadbalancerhttpslistener.go b/pkg/util/aliyun/loadbalancerhttpslistener.go index 6589f973fa..aba7729869 100644 --- a/pkg/util/aliyun/loadbalancerhttpslistener.go +++ b/pkg/util/aliyun/loadbalancerhttpslistener.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type SLoadbalancerHTTPSListener struct { @@ -14,6 +15,7 @@ type SLoadbalancerHTTPSListener struct { BackendServerPort int // 负载均衡实例后端使用的端口。 Bandwidth int // 监听的带宽峰值。 Status string // 当前监听的状态。取值:starting | running | configuring | stopping | stopped + Description string XForwardedFor string // 是否开启通过X-Forwarded-For头字段获取访者真实IP。 XForwardedFor_SLBIP string // 是否通过SLB-IP头字段获取客户端请求的真实IP。 @@ -51,6 +53,12 @@ type SLoadbalancerHTTPSListener struct { } func (listener *SLoadbalancerHTTPSListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } return fmt.Sprintf("HTTPS:%d", listener.ListenerPort) } @@ -63,7 +71,14 @@ func (listerner *SLoadbalancerHTTPSListener) GetGlobalId() string { } func (listerner *SLoadbalancerHTTPSListener) GetStatus() string { - return listerner.Status + switch listerner.Status { + case "starting", "running": + return models.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return models.LB_STATUS_DISABLED + default: + return models.LB_STATUS_UNKNOWN + } } func (listerner *SLoadbalancerHTTPSListener) GetMetadata() *jsonutils.JSONDict { @@ -75,7 +90,11 @@ func (listerner *SLoadbalancerHTTPSListener) IsEmulated() bool { } func (listerner *SLoadbalancerHTTPSListener) Refresh() error { - return nil + lis, err := listerner.lb.region.GetLoadbalancerHTTPSListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) } func (listerner *SLoadbalancerHTTPSListener) GetListenerType() string { @@ -87,7 +106,14 @@ func (listerner *SLoadbalancerHTTPSListener) GetListenerPort() int { } func (listerner *SLoadbalancerHTTPSListener) GetBackendGroupId() string { - return "" + if len(listerner.VServerGroupId) == 0 { + listerner.Refresh() + } + return listerner.VServerGroupId +} + +func (listerner *SLoadbalancerHTTPSListener) GetBackendServerPort() int { + return listerner.BackendServerPort } func (listerner *SLoadbalancerHTTPSListener) GetScheduler() string { @@ -220,3 +246,106 @@ func (region *SRegion) GetLoadbalancerHTTPSListener(loadbalancerId string, liste listener := SLoadbalancerHTTPSListener{} return &listener, body.Unmarshal(&listener) } + +func (region *SRegion) constructHTTPCreateListenerParams(params map[string]string, listener *cloudprovider.SLoadbalancerListener) map[string]string { + params["HealthCheck"] = listener.HealthCheck + if listener.HealthCheck == "on" { + if len(listener.HealthCheckURI) == 0 { + params["HealthCheckURI"] = "/" + } + //The HealthCheckTimeout parameter is required. + if listener.HealthCheckTimeout < 1 || listener.HealthCheckTimeout > 300 { + listener.HealthCheckTimeout = 5 + } + params["HealthCheckTimeout"] = fmt.Sprintf("%d", listener.HealthCheckTimeout) + } + params["StickySession"] = listener.StickySession + params["StickySessionType"] = listener.StickySessionType + params["Cookie"] = listener.StickySessionCookie + if listener.StickySessionCookieTimeout < 1 || listener.StickySessionCookieTimeout > 86400 { + listener.StickySessionCookieTimeout = 500 + } + params["CookieTimeout"] = fmt.Sprintf("%d", listener.StickySessionCookieTimeout) + //params["ForwardPort"] = fmt.Sprintf("%d", listener.ForwardPort) //暂不支持 + params["Gzip"] = "off" + if listener.Gzip { + params["Gzip"] = "on" + } + params["XForwardedFor"] = "off" + if listener.XForwardedFor { + params["XForwardedFor"] = "on" + } + return params +} + +func (region *SRegion) CreateLoadbalancerHTTPSListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + params["ServerCertificateId"] = listener.CertificateID + if len(listener.TLSCipherPolicy) > 0 { + params["TLSCipherPolicy"] = listener.TLSCipherPolicy + } + _, err := region.lbRequest("CreateLoadBalancerHTTPSListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerHTTPSListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerHTTPSListener) Delete() error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerHTTPSListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + _rule := &SLoadbalancerListenerRule{ + Domain: rule.Domain, + Url: rule.Path, + RuleName: rule.Name, + } + if len(rule.BackendGroupID) > 0 { //&& rule.BackendGroupType == models.LB_BACKENDGROUP_TYPE_NORMAL { + _rule.VServerGroupId = rule.BackendGroupID + } + listenerRule, err := listerner.lb.region.CreateLoadbalancerListenerRule(listerner.ListenerPort, listerner.lb.LoadBalancerId, _rule) + if err != nil { + return nil, err + } + listenerRule.httpsListener = listerner + return listenerRule, nil +} + +func (listerner *SLoadbalancerHTTPSListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + rule, err := listerner.lb.region.GetLoadbalancerListenerRule(ruleId) + if err != nil { + return nil, err + } + rule.httpsListener = listerner + return rule, nil +} + +func (listerner *SLoadbalancerHTTPSListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerHTTPSListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerHTTPSListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + params = region.constructHTTPCreateListenerParams(params, listener) + params["ServerCertificateId"] = listener.CertificateID + if len(lb.LoadBalancerSpec) > 0 && len(listener.TLSCipherPolicy) > 0 { + params["TLSCipherPolicy"] = listener.TLSCipherPolicy + } + _, err := region.lbRequest("SetLoadBalancerHTTPSListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerHTTPSListener) Sync(lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerHTTPSListener(listerner.lb, lblis) +} diff --git a/pkg/util/aliyun/loadbalancerlistenerrule.go b/pkg/util/aliyun/loadbalancerlistenerrule.go index 8a49013e10..ac69dbefdc 100644 --- a/pkg/util/aliyun/loadbalancerlistenerrule.go +++ b/pkg/util/aliyun/loadbalancerlistenerrule.go @@ -4,18 +4,19 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" ) type SLoadbalancerListenerRule struct { httpListener *SLoadbalancerHTTPListener httpsListener *SLoadbalancerHTTPSListener - Domain string + Domain string `json:"Domain"` ListenerSync string RuleId string - RuleName string - Url string - VServerGroupId string + RuleName string `json:"RuleName"` + Url string `json:"Url"` + VServerGroupId string `json:"VServerGroupId"` } func (lbr *SLoadbalancerListenerRule) GetName() string { @@ -42,10 +43,27 @@ func (lbr *SLoadbalancerListenerRule) IsEmulated() bool { return false } -func (lbr *SLoadbalancerListenerRule) Refresh() error { +func (lbr *SLoadbalancerListenerRule) getRegion() *SRegion { + if lbr.httpListener != nil { + return lbr.httpListener.lb.region + } else if lbr.httpsListener != nil { + return lbr.httpsListener.lb.region + } return nil } +func (lbr *SLoadbalancerListenerRule) Refresh() error { + region := lbr.getRegion() + if region == nil { + return fmt.Errorf("failed to find listener for rule %s", lbr.RuleName) + } + rule, err := region.GetLoadbalancerListenerRule(lbr.RuleId) + if err != nil { + return err + } + return jsonutils.Update(lbr, rule) +} + func (lbr *SLoadbalancerListenerRule) GetDomain() string { return lbr.Domain } @@ -70,3 +88,60 @@ func (region *SRegion) GetLoadbalancerListenerRules(loadbalancerId string, liste rules := []SLoadbalancerListenerRule{} return rules, body.Unmarshal(&rules, "Rules", "Rule") } + +func (lbr *SLoadbalancerListenerRule) Delete() error { + if lbr.httpListener != nil { + return lbr.httpListener.lb.region.DeleteLoadbalancerListenerRule(lbr.RuleId) + } + if lbr.httpsListener != nil { + return lbr.httpsListener.lb.region.DeleteLoadbalancerListenerRule(lbr.RuleId) + } + return fmt.Errorf("failed to find listener for listener rule %s", lbr.RuleName) +} + +func (region *SRegion) DeleteLoadbalancerListenerRule(ruleId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["RuleIds"] = fmt.Sprintf(`["%s"]`, ruleId) + _, err := region.lbRequest("DeleteRules", params) + return err +} + +func (region *SRegion) CreateLoadbalancerListenerRule(listenerPort int, loadbalancerId string, _rule *SLoadbalancerListenerRule) (*SLoadbalancerListenerRule, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ListenerPort"] = fmt.Sprintf("%d", listenerPort) + params["LoadBalancerId"] = loadbalancerId + _rules := jsonutils.NewArray() + _rules.Add(jsonutils.Marshal(_rule)) + params["RuleList"] = _rules.String() + body, err := region.lbRequest("CreateRules", params) + if err != nil { + return nil, err + } + rules := []SLoadbalancerListenerRule{} + if err := body.Unmarshal(&rules, "Rules", "Rule"); err != nil { + return nil, err + } + for _, rule := range rules { + if rule.RuleName == _rule.RuleName { + return region.GetLoadbalancerListenerRule(rule.RuleId) + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) GetLoadbalancerListenerRule(ruleId string) (*SLoadbalancerListenerRule, error) { + if len(ruleId) == 0 { + return nil, cloudprovider.ErrNotFound + } + params := map[string]string{} + params["RegionId"] = region.RegionId + params["RuleId"] = ruleId + body, err := region.lbRequest("DescribeRuleAttribute", params) + if err != nil { + return nil, err + } + rule := &SLoadbalancerListenerRule{RuleId: ruleId} + return rule, body.Unmarshal(rule) +} diff --git a/pkg/util/aliyun/loadbalancermasterslavebackendgroup.go b/pkg/util/aliyun/loadbalancermasterslavebackendgroup.go index 577f4d1056..fe30a8e34a 100644 --- a/pkg/util/aliyun/loadbalancermasterslavebackendgroup.go +++ b/pkg/util/aliyun/loadbalancermasterslavebackendgroup.go @@ -1,8 +1,11 @@ package aliyun import ( + "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type SLoadbalancerMasterSlaveBackendGroup struct { @@ -25,7 +28,7 @@ func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetGlobalId() string { } func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetStatus() string { - return "" + return models.LB_STATUS_ENABLED } func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetMetadata() *jsonutils.JSONDict { @@ -45,7 +48,7 @@ func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) IsDefault() bool { } func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetType() string { - return "master_slave" + return models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE } func (region *SRegion) GetLoadbalancerMasterSlaveBackendgroups(loadbalancerId string) ([]SLoadbalancerMasterSlaveBackendGroup, error) { @@ -84,3 +87,76 @@ func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) GetILoadbalancerBacken } return ibackends, nil } + +func (region *SRegion) CreateLoadbalancerMasterSlaveBackendGroup(name, loadbalancerId string, backends []cloudprovider.SLoadbalancerBackend) (*SLoadbalancerMasterSlaveBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupName"] = name + params["LoadBalancerId"] = loadbalancerId + if len(backends) != 2 { + return nil, fmt.Errorf("master slave backendgorup must contain two backend") + } + servers := jsonutils.NewArray() + for _, backend := range backends { + serverType := "Slave" + if backend.Index == 0 { + serverType = "Master" + } + servers.Add( + jsonutils.Marshal( + map[string]string{ + "ServerId": backend.ExternalID, + "Port": fmt.Sprintf("%d", backend.Port), + "Weight": fmt.Sprintf("%d", backend.Weight), + "ServerType": serverType, + }, + )) + } + params["MasterSlaveBackendServers"] = servers.String() + body, err := region.lbRequest("CreateMasterSlaveServerGroup", params) + if err != nil { + return nil, err + } + groupId, err := body.GetString("MasterSlaveServerGroupId") + if err != nil { + return nil, err + } + return region.GetLoadbalancerMasterSlaveBackendgroupById(groupId) +} + +func (region *SRegion) GetLoadbalancerMasterSlaveBackendgroupById(groupId string) (*SLoadbalancerMasterSlaveBackendGroup, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupId"] = groupId + params["NeedInstanceDetail"] = "true" + body, err := region.lbRequest("DescribeMasterSlaveServerGroupAttribute", params) + if err != nil { + return nil, err + } + group := &SLoadbalancerMasterSlaveBackendGroup{} + return group, body.Unmarshal(group) +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) Sync(name string) error { + return nil +} + +func (region *SRegion) DeleteLoadbalancerMasterSlaveBackendgroup(groupId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["MasterSlaveServerGroupId"] = groupId + _, err := region.lbRequest("DeleteMasterSlaveServerGroup", params) + return err +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) Delete() error { + return backendgroup.lb.region.DeleteLoadbalancerMasterSlaveBackendgroup(backendgroup.MasterSlaveServerGroupId) +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (backendgroup *SLoadbalancerMasterSlaveBackendGroup) RemoveBackendServer(serverId string, weight, port int) error { + return cloudprovider.ErrNotSupported +} diff --git a/pkg/util/aliyun/loadbalancerservercertificate.go b/pkg/util/aliyun/loadbalancerservercertificate.go index 65818ea5c8..e7c2ec37f3 100644 --- a/pkg/util/aliyun/loadbalancerservercertificate.go +++ b/pkg/util/aliyun/loadbalancerservercertificate.go @@ -75,6 +75,34 @@ func (certificate *SLoadbalancerServerCertificate) Refresh() error { return nil } +func (region *SRegion) UpdateServerCertificateName(certId, name string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ServerCertificateId"] = certId + params["ServerCertificateName"] = name + _, err := region.lbRequest("SetServerCertificateName", params) + return err +} + +func (certificate *SLoadbalancerServerCertificate) Sync(name string, privateKey string, publicKey string) error { + if certificate.ServerCertificateName != name { + return certificate.region.UpdateServerCertificateName(certificate.ServerCertificateId, name) + } + return nil +} + +func (certificate *SLoadbalancerServerCertificate) Delete() error { + return certificate.region.DeleteServerCertificate(certificate.ServerCertificateId) +} + +func (region *SRegion) DeleteServerCertificate(certId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ServerCertificateId"] = certId + _, err := region.lbRequest("DeleteServerCertificate", params) + return err +} + func (region *SRegion) GetLoadbalancerServerCertificates() ([]SLoadbalancerServerCertificate, error) { params := map[string]string{} params["RegionId"] = region.RegionId diff --git a/pkg/util/aliyun/loadbalancertcplistener.go b/pkg/util/aliyun/loadbalancertcplistener.go index 9214fb0b49..3b8f2492b9 100644 --- a/pkg/util/aliyun/loadbalancertcplistener.go +++ b/pkg/util/aliyun/loadbalancertcplistener.go @@ -5,6 +5,8 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/pkg/utils" ) type SLoadbalancerTCPListener struct { @@ -14,11 +16,12 @@ type SLoadbalancerTCPListener struct { BackendServerPort int // 负载均衡实例后端使用的端口。 Bandwidth int // 监听的带宽峰值。 Status string // 当前监听的状态,取值:starting | running | configuring | stopping | stopped + Description string - Scheduler string // 调度算法。 - VServerGroupId string // 绑定的服务器组ID。 - MaterSlaveServerGroupId string // 绑定的主备服务器组ID。 - AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) + Scheduler string // 调度算法。 + VServerGroupId string // 绑定的服务器组ID。 + MasterSlaveServerGroupId string // 绑定的主备服务器组ID。 + AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) AclType string // 访问控制类型 @@ -33,6 +36,12 @@ type SLoadbalancerTCPListener struct { } func (listener *SLoadbalancerTCPListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } return fmt.Sprintf("TCP:%d", listener.ListenerPort) } @@ -45,7 +54,14 @@ func (listerner *SLoadbalancerTCPListener) GetGlobalId() string { } func (listerner *SLoadbalancerTCPListener) GetStatus() string { - return listerner.Status + switch listerner.Status { + case "starting", "running": + return models.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return models.LB_STATUS_DISABLED + default: + return models.LB_STATUS_UNKNOWN + } } func (listerner *SLoadbalancerTCPListener) GetMetadata() *jsonutils.JSONDict { @@ -57,7 +73,11 @@ func (listerner *SLoadbalancerTCPListener) IsEmulated() bool { } func (listerner *SLoadbalancerTCPListener) Refresh() error { - return nil + lis, err := listerner.lb.region.GetLoadbalancerTCPListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) } func (listerner *SLoadbalancerTCPListener) GetListenerType() string { @@ -71,7 +91,7 @@ func (listerner *SLoadbalancerTCPListener) GetBackendGroupId() string { if len(listerner.VServerGroupId) > 0 { return listerner.VServerGroupId } - return listerner.MaterSlaveServerGroupId + return listerner.MasterSlaveServerGroupId } func (listerner *SLoadbalancerTCPListener) GetScheduler() string { @@ -170,6 +190,10 @@ func (listerner *SLoadbalancerTCPListener) HTTP2Enabled() bool { return false } +func (listerner *SLoadbalancerTCPListener) GetBackendServerPort() int { + return listerner.BackendServerPort +} + func (listerner *SLoadbalancerTCPListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { return []cloudprovider.ICloudLoadbalancerListenerRule{}, nil } @@ -186,3 +210,121 @@ func (region *SRegion) GetLoadbalancerTCPListener(loadbalancerId string, listene listener := SLoadbalancerTCPListener{} return &listener, body.Unmarshal(&listener) } + +func (region *SRegion) constructBaseCreateListenerParams(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) map[string]string { + params := map[string]string{} + params["RegionId"] = region.RegionId + switch lb.InternetChargeType { + case "paybytraffic": + params["Bandwidth"] = "-1" + case "paybybandwidth": + if lb.Bandwidth > 5000 { + lb.Bandwidth = 5000 + } + params["Bandwidth"] = fmt.Sprintf("%d", lb.Bandwidth) + default: + params["Bandwidth"] = fmt.Sprintf("%d", listener.Bandwidth) + } + params["ListenerPort"] = fmt.Sprintf("%d", listener.ListenerPort) + params["LoadBalancerId"] = lb.LoadBalancerId + if len(listener.AccessControlListID) > 0 { + params["AclId"] = listener.AccessControlListID + } + if utils.IsInStringArray(listener.AccessControlListStatus, []string{"on", "off"}) { + params["AclStatus"] = listener.AccessControlListStatus + } + if utils.IsInStringArray(listener.AccessControlListType, []string{"white", "black"}) { + params["AclType"] = listener.AccessControlListType + } + switch listener.BackendGroupType { + case models.LB_BACKENDGROUP_TYPE_NORMAL: + params["VServerGroupId"] = listener.BackendGroupID + case models.LB_BACKENDGROUP_TYPE_MASTER_SLAVE: + params["MasterSlaveServerGroupId"] = listener.BackendGroupID + case models.LB_BACKENDGROUP_TYPE_DEFAULT: + params["BackendServerPort"] = fmt.Sprintf("%d", listener.BackendServerPort) + } + if len(listener.Name) > 0 { + params["Description"] = listener.Name + } + if listener.EstablishedTimeout >= 10 && listener.EstablishedTimeout <= 900 { + params["EstablishedTimeout"] = fmt.Sprintf("%d", listener.EstablishedTimeout) + } + + if utils.IsInStringArray(listener.ListenerType, []string{models.LB_LISTENER_TYPE_TCP, models.LB_LISTENER_TYPE_UDP}) { + if listener.HealthCheckTimeout >= 1 && listener.HealthCheckTimeout <= 300 { + params["HealthCheckConnectTimeout"] = fmt.Sprintf("%d", listener.HealthCheckTimeout) + } + } + + if len(listener.HealthCheckDomain) > 0 { + params["HealthCheckDomain"] = listener.HealthCheckDomain + } + + if len(listener.HealthCheckHttpCode) > 0 { + params["HealthCheckHttpCode"] = listener.HealthCheckHttpCode + } + + if len(listener.HealthCheckURI) > 0 { + params["HealthCheckURI"] = listener.HealthCheckURI + } + + if listener.HealthCheckRise >= 2 && listener.HealthCheckRise <= 10 { + params["HealthyThreshold"] = fmt.Sprintf("%d", listener.HealthCheckRise) + } + + if listener.HealthCheckFail >= 2 && listener.HealthCheckFail <= 10 { + params["UnhealthyThreshold"] = fmt.Sprintf("%d", listener.HealthCheckFail) + } + + if listener.HealthCheckInterval >= 1 && listener.HealthCheckInterval <= 50 { + params["healthCheckInterval"] = fmt.Sprintf("%d", listener.HealthCheckInterval) + } + + params["Scheduler"] = listener.Scheduler + return params +} + +func (region *SRegion) CreateLoadbalancerTCPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("CreateLoadBalancerTCPListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerTCPListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerTCPListener) Delete() error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerTCPListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerTCPListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerTCPListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerTCPListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerTCPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("SetLoadBalancerTCPListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerTCPListener) Sync(lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerTCPListener(listerner.lb, lblis) +} diff --git a/pkg/util/aliyun/loadbalancerudplistener.go b/pkg/util/aliyun/loadbalancerudplistener.go index d3912f71fc..ef51e5a642 100644 --- a/pkg/util/aliyun/loadbalancerudplistener.go +++ b/pkg/util/aliyun/loadbalancerudplistener.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type SLoadbalancerUDPListener struct { @@ -14,11 +15,12 @@ type SLoadbalancerUDPListener struct { BackendServerPort int // 负载均衡实例后端使用的端口。 Bandwidth int // 监听的带宽峰值。 Status string // 当前监听的状态,取值:starting | running | configuring | stopping | stopped + Description string - Scheduler string // 调度算法 - VServerGroupId string // 绑定的服务器组ID。 - MaterSlaveServerGroupId string // 绑定的主备服务器组ID。 - AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) + Scheduler string // 调度算法 + VServerGroupId string // 绑定的服务器组ID。 + MasterSlaveServerGroupId string // 绑定的主备服务器组ID。 + AclStatus string // 是否开启访问控制功能。取值:on | off(默认值) AclType string // 访问控制类型: @@ -33,6 +35,12 @@ type SLoadbalancerUDPListener struct { } func (listener *SLoadbalancerUDPListener) GetName() string { + if len(listener.Description) == 0 { + listener.Refresh() + } + if len(listener.Description) > 0 { + return listener.Description + } return fmt.Sprintf("UDP:%d", listener.ListenerPort) } @@ -45,7 +53,14 @@ func (listerner *SLoadbalancerUDPListener) GetGlobalId() string { } func (listerner *SLoadbalancerUDPListener) GetStatus() string { - return listerner.Status + switch listerner.Status { + case "starting", "running": + return models.LB_STATUS_ENABLED + case "configuring", "stopping", "stopped": + return models.LB_STATUS_DISABLED + default: + return models.LB_STATUS_UNKNOWN + } } func (listerner *SLoadbalancerUDPListener) GetMetadata() *jsonutils.JSONDict { @@ -57,7 +72,11 @@ func (listerner *SLoadbalancerUDPListener) IsEmulated() bool { } func (listerner *SLoadbalancerUDPListener) Refresh() error { - return nil + lis, err := listerner.lb.region.GetLoadbalancerUDPListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) + if err != nil { + return err + } + return jsonutils.Update(listerner, lis) } func (listerner *SLoadbalancerUDPListener) GetListenerType() string { @@ -72,7 +91,11 @@ func (listerner *SLoadbalancerUDPListener) GetBackendGroupId() string { if len(listerner.VServerGroupId) > 0 { return listerner.VServerGroupId } - return listerner.MaterSlaveServerGroupId + return listerner.MasterSlaveServerGroupId +} + +func (listerner *SLoadbalancerUDPListener) GetBackendServerPort() int { + return listerner.BackendServerPort } func (listerner *SLoadbalancerUDPListener) GetScheduler() string { @@ -187,3 +210,47 @@ func (region *SRegion) GetLoadbalancerUDPListener(loadbalancerId string, listene listener := SLoadbalancerUDPListener{} return &listener, body.Unmarshal(&listener) } + +func (region *SRegion) CreateLoadbalancerUDPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("CreateLoadBalancerUDPListener", params) + if err != nil { + return nil, err + } + iListener, err := region.GetLoadbalancerUDPListener(lb.LoadBalancerId, listener.ListenerPort) + if err != nil { + return nil, err + } + iListener.lb = lb + return iListener, nil +} + +func (listerner *SLoadbalancerUDPListener) Delete() error { + return listerner.lb.region.DeleteLoadbalancerListener(listerner.lb.LoadBalancerId, listerner.ListenerPort) +} + +func (listerner *SLoadbalancerUDPListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerUDPListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (listerner *SLoadbalancerUDPListener) Start() error { + return listerner.lb.region.startListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (listerner *SLoadbalancerUDPListener) Stop() error { + return listerner.lb.region.stopListener(listerner.ListenerPort, listerner.lb.LoadBalancerId) +} + +func (region *SRegion) SyncLoadbalancerUDPListener(lb *SLoadbalancer, listener *cloudprovider.SLoadbalancerListener) error { + params := region.constructBaseCreateListenerParams(lb, listener) + _, err := region.lbRequest("SetLoadBalancerUDPListenerAttribute", params) + return err +} + +func (listerner *SLoadbalancerUDPListener) Sync(lblis *cloudprovider.SLoadbalancerListener) error { + return listerner.lb.region.SyncLoadbalancerUDPListener(listerner.lb, lblis) +} diff --git a/pkg/util/aliyun/natgateway.go b/pkg/util/aliyun/natgateway.go new file mode 100644 index 0000000000..48ba8d9799 --- /dev/null +++ b/pkg/util/aliyun/natgateway.go @@ -0,0 +1,166 @@ +package aliyun + +import ( + "fmt" + + "time" + "yunion.io/x/log" +) + +type SBandwidthPackageIds struct { + BandwidthPackageId []string +} + +type SForwardTableIds struct { + ForwardTableId []string +} + +type SSnatTableIds struct { + SnatTableId []string +} + +type SNatGetway struct { + vpc *SVpc + + BandwidthPackageIds SBandwidthPackageIds + BusinessStatus string + CreationTime time.Time + Description string + ForwardTableIds SForwardTableIds + SnatTableIds SSnatTableIds + InstanceChargeType string + Name string + NatGatewayId string + RegionId string + Spec string + Status string + VpcId string +} + +func (self *SRegion) GetNatGateways(vpcId string, natGwId string, offset, limit int) ([]SNatGetway, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if len(vpcId) > 0 { + params["VpcId"] = vpcId + } + if len(natGwId) > 0 { + params["NatGatewayId"] = natGwId + } + + body, err := self.vpcRequest("DescribeNatGateways", params) + if err != nil { + log.Errorf("GetVSwitches fail %s", err) + return nil, 0, err + } + + if self.client.Debug { + log.Debugf("%s", body.PrettyString()) + } + + gateways := make([]SNatGetway, 0) + err = body.Unmarshal(&gateways, "NatGateways", "NatGateway") + if err != nil { + log.Errorf("Unmarshal gateways fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return gateways, int(total), nil +} + +type SSNATTableEntry struct { + SnatEntryId string + SnatIp string + SnatTableId string `json:"snat_table_id"` + SourceCIDR string `json:"source_cidr"` + SourceVSwitchId string `json:"source_vswitch_id"` + Status string +} + +func (self *SRegion) GetSNATEntries(tableId string, offset, limit int) ([]SSNATTableEntry, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + params["SnatTableId"] = tableId + + body, err := self.vpcRequest("DescribeSnatTableEntries", params) + if err != nil { + log.Errorf("DescribeSnatTableEntries fail %s", err) + return nil, 0, err + } + + if self.client.Debug { + log.Debugf("%s", body.PrettyString()) + } + + entries := make([]SSNATTableEntry, 0) + err = body.Unmarshal(&entries, "SnatTableEntries", "SnatTableEntry") + if err != nil { + log.Errorf("Unmarshal entries fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return entries, int(total), nil +} + +func (region *SRegion) DeleteSnatEntry(tableId string, entryId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["SnatTableId"] = tableId + params["SnatEntryId"] = entryId + _, err := region.vpcRequest("DeleteSnatEntry", params) + return err +} + +func (nat *SNatGetway) getSnatEntriesForTable(tblId string) ([]SSNATTableEntry, error) { + entries := make([]SSNATTableEntry, 0) + entryTotal := -1 + for entryTotal < 0 || len(entries) < entryTotal { + parts, total, err := nat.vpc.region.GetSNATEntries(tblId, len(entries), 50) + if err != nil { + return nil, err + } + if len(parts) > 0 { + entries = append(entries, parts...) + } + entryTotal = total + } + return entries, nil +} + +func (nat *SNatGetway) getSnatEntries() ([]SSNATTableEntry, error) { + entries := make([]SSNATTableEntry, 0) + for i := range nat.SnatTableIds.SnatTableId { + sentries, err := nat.getSnatEntriesForTable(nat.SnatTableIds.SnatTableId[i]) + if err != nil { + return nil, err + } + entries = append(entries, sentries...) + } + return entries, nil +} + +func (nat *SNatGetway) dissociateWithVswitch(vswitchId string) error { + entries, err := nat.getSnatEntries() + if err != nil { + return err + } + for i := range entries { + log.Debugf("%s", entries[i]) + if entries[i].SourceVSwitchId == vswitchId { + err := nat.vpc.region.DeleteSnatEntry(entries[i].SnatTableId, entries[i].SnatEntryId) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/util/aliyun/provider/provider.go b/pkg/util/aliyun/provider/provider.go index 3cbc985c7b..d64a04a349 100644 --- a/pkg/util/aliyun/provider/provider.go +++ b/pkg/util/aliyun/provider/provider.go @@ -1,9 +1,13 @@ package provider import ( + "context" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/aliyun" ) @@ -19,6 +23,37 @@ func (self *SAliyunProviderFactory) ValidateChangeBandwidth(instanceId string, b return nil } +func (self *SAliyunProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) error { + accessKeyID, _ := data.GetString("access_key_id") + if len(accessKeyID) == 0 { + return httperrors.NewMissingParameterError("access_key_id") + } + accessKeySecret, _ := data.GetString("access_key_secret") + if len(accessKeySecret) == 0 { + return httperrors.NewMissingParameterError("access_key_secret") + } + data.Set("account", jsonutils.NewString(accessKeyID)) + data.Set("secret", jsonutils.NewString(accessKeySecret)) + return nil +} + +func (self *SAliyunProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, cloudaccount string) (*cloudprovider.SCloudaccount, error) { + + accessKeyID, _ := data.GetString("access_key_id") + if len(accessKeyID) == 0 { + return nil, httperrors.NewMissingParameterError("access_key_id") + } + accessKeySecret, _ := data.GetString("access_key_secret") + if len(accessKeySecret) == 0 { + return nil, httperrors.NewMissingParameterError("access_key_secret") + } + account := &cloudprovider.SCloudaccount{ + Account: accessKeyID, + Secret: accessKeySecret, + } + return account, nil +} + func (self *SAliyunProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) { /* provider, ok := self.providerTable[providerId] if ok { @@ -37,7 +72,7 @@ func (self *SAliyunProviderFactory) GetProvider(providerId, providerName, url, a return self.providerTable[providerId], nil */ - client, err := aliyun.NewAliyunClient(providerId, providerName, account, secret) + client, err := aliyun.NewAliyunClient(providerId, providerName, account, secret, false) if err != nil { return nil, err } @@ -106,3 +141,7 @@ func (self *SAliyunProvider) GetBalance() (float64, error) { func (self *SAliyunProvider) GetOnPremiseIRegion() (cloudprovider.ICloudRegion, error) { return nil, cloudprovider.ErrNotImplemented } + +func (self *SAliyunProvider) SupportPrepaidResources() bool { + return true +} diff --git a/pkg/util/aliyun/region.go b/pkg/util/aliyun/region.go index e8956fc397..de46afcacb 100644 --- a/pkg/util/aliyun/region.go +++ b/pkg/util/aliyun/region.go @@ -3,12 +3,14 @@ package aliyun import ( "fmt" "strings" + "time" "github.com/aliyun/alibaba-cloud-sdk-go/sdk" "github.com/aliyun/aliyun-oss-go-sdk/oss" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/regutils" "yunion.io/x/pkg/util/secrules" "yunion.io/x/pkg/utils" @@ -21,6 +23,8 @@ type SRegion struct { sdkClient *sdk.Client ossClient *oss.Client + Debug bool + RegionId string LocalName string @@ -28,6 +32,8 @@ type SRegion struct { ivpcs []cloudprovider.ICloudVpc + lbEndpints map[string]string + storageCache *SStoragecache instanceTypes []SInstanceType @@ -87,12 +93,62 @@ func (self *SRegion) ecsRequest(apiName string, params map[string]string) (jsonu return jsonRequest(client, "ecs.aliyuncs.com", ALIYUN_API_VERSION, apiName, params) } +func (self *SRegion) vpcRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + return jsonRequest(client, "vpc.aliyuncs.com", ALIYUN_API_VERSION_VPC, action, params) +} + +type LBRegion struct { + RegionEndpoint string + RegionId string +} + +func (self *SRegion) fetchLBRegions(client *sdk.Client) error { + if len(self.lbEndpints) > 0 { + return nil + } + params := map[string]string{} + result, err := self._lbRequest(client, "DescribeRegions", "slb.aliyuncs.com", params) + if err != nil { + return err + } + self.lbEndpints = map[string]string{} + regions := []LBRegion{} + if err := result.Unmarshal(®ions, "Regions", "Region"); err != nil { + return err + } + for _, region := range regions { + self.lbEndpints[region.RegionId] = region.RegionEndpoint + } + return nil +} + func (self *SRegion) lbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { client, err := self.getSdkClient() if err != nil { return nil, err } - return jsonRequest(client, "slb.aliyuncs.com", ALIYUN_API_VERSION_LB, apiName, params) + domain := "slb.aliyuncs.com" + if !utils.IsInStringArray(apiName, []string{"DescribeRegions", "DescribeZones"}) { + if regionId, ok := params["RegionId"]; ok { + if err := self.fetchLBRegions(client); err != nil { + return nil, err + } + endpoint, ok := self.lbEndpints[regionId] + if !ok { + return nil, fmt.Errorf("failed to find endpoint for lb region %s", regionId) + } + domain = endpoint + } + } + return self._lbRequest(client, apiName, domain, params) +} + +func (self *SRegion) _lbRequest(client *sdk.Client, apiName string, domain string, params map[string]string) (jsonutils.JSONObject, error) { + return jsonRequest(client, domain, ALIYUN_API_VERSION_LB, apiName, params) } ///////////////////////////////////////////////////////////////////////////// @@ -365,37 +421,6 @@ func (self *SRegion) GetRouteTables(ids []string, offset int, limit int) ([]SRou return routetables, int(total), nil } -func (self *SRegion) GetVSwitches(ids []string, vpcId string, offset int, limit int) ([]SVSwitch, int, error) { - if limit > 50 || limit <= 0 { - limit = 50 - } - params := make(map[string]string) - params["RegionId"] = self.RegionId - params["PageSize"] = fmt.Sprintf("%d", limit) - params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) - if ids != nil && len(ids) > 0 { - params["VSwitchId"] = strings.Join(ids, ",") - } - if len(vpcId) > 0 { - params["VpcId"] = vpcId - } - - body, err := self.ecsRequest("DescribeVSwitches", params) - if err != nil { - log.Errorf("GetVSwitches fail %s", err) - return nil, 0, err - } - - switches := make([]SVSwitch, 0) - err = body.Unmarshal(&switches, "VSwitches", "VSwitch") - if err != nil { - log.Errorf("Unmarshal vswitches fail %s", err) - return nil, 0, err - } - total, _ := body.Int("TotalCount") - return switches, int(total), nil -} - func (self *SRegion) GetMatchInstanceTypes(cpu int, memMB int, gpu int, zoneId string) ([]SInstanceType, error) { if self.instanceTypes == nil { types, err := self.GetInstanceTypes() @@ -434,7 +459,21 @@ func (self *SRegion) CreateInstanceSimple(name string, imgId string, cpu int, me log.Debugf("Search in zone %s", z.LocalName) net := z.getNetworkById(vswitchId) if net != nil { - inst, err := z.getHost().CreateVM(name, imgId, 0, cpu, memGB*1024, vswitchId, "", "", passwd, storageType, dataDiskSizesGB, publicKey, "", "", nil) + desc := &cloudprovider.SManagedVMCreateConfig{ + Name: name, + ExternalImageId: imgId, + SysDisk: cloudprovider.SDiskInfo{SizeGB: 0, StorageType: storageType}, + Cpu: cpu, + MemoryMB: memGB * 1024, + ExternalNetworkId: vswitchId, + Password: passwd, + DataDisks: []cloudprovider.SDiskInfo{}, + PublicKey: publicKey, + } + for _, sizeGB := range dataDiskSizesGB { + desc.DataDisks = append(desc.DataDisks, cloudprovider.SDiskInfo{SizeGB: sizeGB, StorageType: storageType}) + } + inst, err := z.getHost().CreateVM(desc) if err != nil { return nil, err } @@ -586,14 +625,6 @@ func (self *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) { return iStores, nil } -func (self *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { - storageCache := self.getStoragecache() - if storageCache.GetGlobalId() == id { - return self.storageCache, nil - } - return nil, cloudprovider.ErrNotFound -} - func (self *SRegion) updateInstance(instId string, name, desc, passwd, hostname, userData string) error { params := make(map[string]string) params["InstanceId"] = instId @@ -707,8 +738,47 @@ func (region *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, return ilbs, nil } -func (region *SRegion) GetILoadbalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { - acls, err := region.GetLoadbalancerAcls() +func (region *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { + return region.GetLoadbalancerDetail(loadbalancerId) +} + +func (region *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { + certs, err := region.GetLoadbalancerServerCertificates() + if err != nil { + return nil, err + } + for i := 0; i < len(certs); i++ { + if certs[i].GetGlobalId() == certId { + certs[i].region = region + return &certs[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["ServerCertificateName"] = cert.Name + params["PrivateKey"] = cert.PrivateKey + params["ServerCertificate"] = cert.Certificate + body, err := region.lbRequest("UploadServerCertificate", params) + if err != nil { + return nil, err + } + certID, err := body.GetString("ServerCertificateId") + if err != nil { + return nil, err + } + return region.GetILoadBalancerCertificateById(certID) +} + +func (region *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { + return region.GetLoadbalancerAclDetail(aclId) +} + +func (region *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { + acls, err := region.GetLoadBalancerAcls() if err != nil { return nil, err } @@ -720,7 +790,7 @@ func (region *SRegion) GetILoadbalancerAcls() ([]cloudprovider.ICloudLoadbalance return iAcls, nil } -func (region *SRegion) GetILoadbalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { +func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { certificates, err := region.GetLoadbalancerServerCertificates() if err != nil { return nil, err @@ -732,3 +802,89 @@ func (region *SRegion) GetILoadbalancerCertificates() ([]cloudprovider.ICloudLoa } return iCertificates, nil } + +func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["LoadBalancerName"] = loadbalancer.Name + if len(loadbalancer.ZoneID) > 0 { + params["MasterZoneId"] = loadbalancer.ZoneID + } + + if len(loadbalancer.VpcID) > 0 { + params["VpcId"] = loadbalancer.VpcID + } + + if len(loadbalancer.NetworkID) > 0 { + params["VSwitchId"] = loadbalancer.NetworkID + } + + if len(loadbalancer.Address) > 0 { + params["Address"] = loadbalancer.Address + } + + if len(loadbalancer.AddressType) > 0 { + params["AddressType"] = loadbalancer.AddressType + } + + if len(loadbalancer.LoadbalancerSpec) > 0 { + params["LoadBalancerSpec"] = loadbalancer.LoadbalancerSpec + } + + if loadbalancer.Bandwidth > 0 { + params["Bandwidth"] = fmt.Sprintf("%d", loadbalancer.Bandwidth) + } + + body, err := region.lbRequest("CreateLoadBalancer", params) + if err != nil { + return nil, err + } + loadBalancerID, err := body.GetString("LoadBalancerId") + if err != nil { + return nil, err + } + iLoadbalancer, err := region.GetLoadbalancerDetail(loadBalancerID) + if err != nil { + return nil, err + } + return iLoadbalancer, cloudprovider.WaitStatus(iLoadbalancer, models.LB_STATUS_ENABLED, time.Second*5, time.Minute*5) +} + +func (region *SRegion) AddAccessControlListEntry(aclId string, entrys []cloudprovider.SLoadbalancerAccessControlListEntry) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclId"] = aclId + aclArray := jsonutils.NewArray() + for i := 0; i < len(entrys); i++ { + //阿里云AclEntrys参数必须是CIDR格式的。 + if regutils.MatchIPAddr(entrys[i].CIDR) { + entrys[i].CIDR += "/32" + } + aclArray.Add(jsonutils.Marshal(map[string]string{"entry": entrys[i].CIDR, "comment": entrys[i].Comment})) + } + if aclArray.Length() == 0 { + return nil + } + params["AclEntrys"] = aclArray.String() + _, err := region.lbRequest("AddAccessControlListEntry", params) + return err +} + +func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["AclName"] = acl.Name + body, err := region.lbRequest("CreateAccessControlList", params) + if err != nil { + return nil, err + } + aclId, err := body.GetString("AclId") + if err != nil { + return nil, err + } + iAcl, err := region.GetLoadbalancerAclDetail(aclId) + if err != nil { + return nil, err + } + return iAcl, region.AddAccessControlListEntry(aclId, acl.Entrys) +} diff --git a/pkg/util/aliyun/routetable.go b/pkg/util/aliyun/routetable.go index 6f37698cb2..130ce85bb3 100644 --- a/pkg/util/aliyun/routetable.go +++ b/pkg/util/aliyun/routetable.go @@ -7,6 +7,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "strings" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -195,7 +196,7 @@ func (self *SVpc) RemoteGetRouteTableList(offset int, limit int) ([]*SRouteTable params["PageSize"] = fmt.Sprintf("%d", limit) params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) - body, err := self.apiRequest("DescribeRouteTableList", params) + body, err := self.region.vpcRequest("DescribeRouteTableList", params) if err != nil { log.Errorf("RemoteGetRouteTableList fail %s", err) return nil, 0, err @@ -213,3 +214,25 @@ func (self *SVpc) RemoteGetRouteTableList(offset int, limit int) ([]*SRouteTable total, _ := body.Int("TotalCount") return routeTables, int(total), nil } + +func (region *SRegion) AssociateRouteTable(rtableId string, vswitchId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["RouteTableId"] = rtableId + params["VSwitchId"] = vswitchId + _, err := region.vpcRequest("AssociateRouteTable", params) + return err +} + +func (region *SRegion) UnassociateRouteTable(rtableId string, vswitchId string) error { + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["RouteTableId"] = rtableId + params["VSwitchId"] = vswitchId + _, err := region.vpcRequest("UnassociateRouteTable", params) + return err +} + +func (routeTable *SRouteTable) IsSystem() bool { + return strings.ToLower(routeTable.RouteTableType) == "system" +} diff --git a/pkg/util/aliyun/shell/loadbalanceracl.go b/pkg/util/aliyun/shell/loadbalanceracl.go index 8ffa47e22b..298e2cd5ed 100644 --- a/pkg/util/aliyun/shell/loadbalanceracl.go +++ b/pkg/util/aliyun/shell/loadbalanceracl.go @@ -9,7 +9,7 @@ func init() { type LoadbalancerACLListOptions struct { } shellutils.R(&LoadbalancerACLListOptions{}, "lb-acl-list", "List loadbalanceAcls", func(cli *aliyun.SRegion, args *LoadbalancerACLListOptions) error { - acls, err := cli.GetLoadbalancerAcls() + acls, err := cli.GetLoadBalancerAcls() if err != nil { return err } diff --git a/pkg/util/aliyun/shell/natgateway.go b/pkg/util/aliyun/shell/natgateway.go new file mode 100644 index 0000000000..12f50553ad --- /dev/null +++ b/pkg/util/aliyun/shell/natgateway.go @@ -0,0 +1,36 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/util/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NatGatewayListOptions struct { + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&NatGatewayListOptions{}, "natgateway-list", "List NAT gateways", func(cli *aliyun.SRegion, args *NatGatewayListOptions) error { + gws, total, e := cli.GetNatGateways("", "", args.Offset, args.Limit) + if e != nil { + return e + } + printList(gws, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type SNatEntryListOptions struct { + ID string `help:"SNat Table ID"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + shellutils.R(&SNatEntryListOptions{}, "snat-entry-list", "List SNAT entries", func(cli *aliyun.SRegion, args *SNatEntryListOptions) error { + entries, total, e := cli.GetSNATEntries(args.ID, args.Offset, args.Limit) + if e != nil { + return e + } + printList(entries, total, args.Offset, args.Limit, []string{}) + return nil + }) + +} diff --git a/pkg/util/aliyun/shell/vswitch.go b/pkg/util/aliyun/shell/vswitch.go index 568e14e663..b9a0733e42 100644 --- a/pkg/util/aliyun/shell/vswitch.go +++ b/pkg/util/aliyun/shell/vswitch.go @@ -18,4 +18,24 @@ func init() { printList(vswitches, total, args.Offset, args.Limit, []string{}) return nil }) + + type VSwitchShowOptions struct { + ID string `help:"show vswitch details"` + } + shellutils.R(&VSwitchShowOptions{}, "vswitch-show", "Show vswitch details", func(cli *aliyun.SRegion, args *VSwitchShowOptions) error { + vswitch, e := cli.GetVSwitchAttributes(args.ID) + if e != nil { + return e + } + printObject(vswitch) + return nil + }) + + shellutils.R(&VSwitchShowOptions{}, "vswitch-delete", "Show vswitch details", func(cli *aliyun.SRegion, args *VSwitchShowOptions) error { + e := cli.DeleteVSwitch(args.ID) + if e != nil { + return e + } + return nil + }) } diff --git a/pkg/util/aliyun/storage.go b/pkg/util/aliyun/storage.go index 53a5791f09..312455715c 100644 --- a/pkg/util/aliyun/storage.go +++ b/pkg/util/aliyun/storage.go @@ -6,6 +6,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -131,3 +132,10 @@ func (self *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, erro func (self *SStorage) GetMountPoint() string { return "" } + +func (self *SStorage) IsSysDiskStore() bool { + if utils.IsInStringArray(self.storageType, self.zone.getSysDiskCategories()) { + return true + } + return false +} diff --git a/pkg/util/aliyun/storagecache.go b/pkg/util/aliyun/storagecache.go index aaba5a95d2..84ce8dc1c4 100644 --- a/pkg/util/aliyun/storagecache.go +++ b/pkg/util/aliyun/storagecache.go @@ -13,7 +13,6 @@ import ( "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" - compute "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -63,7 +62,7 @@ func (self *SStoragecache) GetManagerId() string { func (self *SStoragecache) fetchImages() error { images := make([]SImage, 0) for { - parts, total, err := self.region.GetImages(ImageStatusType(""), ImageOwnerSelf, nil, "", len(images), 50) + parts, total, err := self.region.GetImages(ImageStatusType(""), "", nil, "", len(images), 50) if err != nil { return err } @@ -91,7 +90,7 @@ func (self *SStoragecache) GetIImages() ([]cloudprovider.ICloudImage, error) { } func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { - parts, _, err := self.region.GetImages(ImageStatusType(""), ImageOwnerSelf, []string{extId}, "", 0, 1) + parts, _, err := self.region.GetImages(ImageStatusType(""), "", []string{extId}, "", 0, 1) if err != nil { return nil, err } @@ -228,7 +227,7 @@ func (self *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDes image.storageCache = self iimage := make([]cloudprovider.ICloudImage, 1) iimage[0] = image - if err := cloudprovider.WaitStatus(iimage[0], compute.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil { + if err := cloudprovider.WaitStatus(iimage[0], cloudprovider.IMAGE_STATUS_ACTIVE, 15*time.Second, 3600*time.Second); err != nil { return nil, err } return iimage[0], nil @@ -355,3 +354,16 @@ func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imag } } } + +func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + storageCache := region.getStoragecache() + return []cloudprovider.ICloudStoragecache{storageCache}, nil +} + +func (region *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { + storageCache := region.getStoragecache() + if id == storageCache.GetGlobalId() { + return storageCache, nil + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/util/aliyun/vpc.go b/pkg/util/aliyun/vpc.go index d8d70350ca..9615f817fe 100644 --- a/pkg/util/aliyun/vpc.go +++ b/pkg/util/aliyun/vpc.go @@ -46,14 +46,6 @@ type SVpc struct { VpcName string } -func (self *SVpc) apiRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { - client, err := self.region.getSdkClient() - if err != nil { - return nil, err - } - return jsonRequest(client, "vpc.aliyuncs.com", ALIYUN_API_VERSION_VPC, action, params) -} - func (self *SVpc) GetMetadata() *jsonutils.JSONDict { return nil } @@ -242,3 +234,22 @@ func (self *SVpc) Delete() error { } return self.region.DeleteVpc(self.VpcId) } + +func (self *SVpc) getNatGateways() ([]SNatGetway, error) { + natgatways := make([]SNatGetway, 0) + gwTotal := -1 + for gwTotal < 0 || len(natgatways) < gwTotal { + parts, total, err := self.region.GetNatGateways(self.VpcId, "", len(natgatways), 50) + if err != nil { + return nil, err + } + if len(parts) > 0 { + natgatways = append(natgatways, parts...) + } + gwTotal = total + } + for i := 0; i < len(natgatways); i += 1 { + natgatways[i].vpc = self + } + return natgatways, nil +} diff --git a/pkg/util/aliyun/vswitch.go b/pkg/util/aliyun/vswitch.go index 944f9323b0..6db526fceb 100644 --- a/pkg/util/aliyun/vswitch.go +++ b/pkg/util/aliyun/vswitch.go @@ -9,6 +9,7 @@ import ( "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/utils" + "fmt" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" ) @@ -20,19 +21,29 @@ const ( VSwitchAvailable = "Available" ) +type SCloudResources struct { + CloudResourceSetType []string +} + type SVSwitch struct { wire *SWire AvailableIpAddressCount int - CidrBlock string - CreationTime time.Time - Description string - IsDefault bool - Status string - VSwitchId string - VSwitchName string - VpcId string - ZoneId string + + CidrBlock string + Ipv6CidrBlock string + CreationTime time.Time + Description string + IsDefault bool + Status string + VSwitchId string + VSwitchName string + VpcId string + ZoneId string + + CloudResources SCloudResources + ResourceGroupId string + RouteTable SRouteTable } func (self *SVSwitch) GetMetadata() *jsonutils.JSONDict { @@ -64,7 +75,7 @@ func (self *SVSwitch) GetStatus() string { func (self *SVSwitch) Refresh() error { log.Debugf("vsiwtch refresh %s", self.VSwitchId) - new, err := self.wire.zone.region.getVSwitch(self.VSwitchId) + new, err := self.wire.zone.region.GetVSwitchAttributes(self.VSwitchId) if err != nil { return err } @@ -104,7 +115,7 @@ func (self *SVSwitch) GetGateway() string { } func (self *SVSwitch) GetServerType() string { - return models.SERVER_TYPE_GUEST + return models.NETWORK_TYPE_GUEST } func (self *SVSwitch) GetIsPublic() bool { @@ -123,37 +134,108 @@ func (self *SRegion) createVSwitch(zoneId string, vpcId string, name string, cid } params["ClientToken"] = utils.GenRequestId(20) - body, err := self.ecsRequest("CreateVSwitch", params) + body, err := self.vpcRequest("CreateVSwitch", params) if err != nil { return "", err } return body.GetString("VSwitchId") } -func (self *SRegion) getVSwitch(vswitchId string) (*SVSwitch, error) { - vswitches, total, err := self.GetVSwitches([]string{vswitchId}, "", 0, 1) - log.Debugf("getVSwitch %d %d %s %s", len(vswitches), total, err, vswitchId) - if err != nil { - return nil, err - } - if total != 1 { - return nil, cloudprovider.ErrNotFound - } - return &vswitches[0], nil -} - -func (self *SRegion) deleteVSwitch(vswitchId string) error { +func (self *SRegion) DeleteVSwitch(vswitchId string) error { params := make(map[string]string) params["VSwitchId"] = vswitchId - _, err := self.ecsRequest("DeleteVSwitch", params) + _, err := self.vpcRequest("DeleteVSwitch", params) return err } func (self *SVSwitch) Delete() error { - return self.wire.zone.region.deleteVSwitch(self.VSwitchId) + err := self.Refresh() + if err != nil { + log.Errorf("refresh vswitch fail %s", err) + return err + } + if len(self.RouteTable.RouteTableId) > 0 && !self.RouteTable.IsSystem() { + err = self.wire.zone.region.UnassociateRouteTable(self.RouteTable.RouteTableId, self.VSwitchId) + if err != nil { + log.Errorf("unassociate routetable fail %s", err) + return err + } + } + err = self.dissociateWithSNAT() + if err != nil { + log.Errorf("fail to dissociateWithSNAT") + return err + } + return self.wire.zone.region.DeleteVSwitch(self.VSwitchId) } func (self *SVSwitch) GetAllocTimeoutSeconds() int { return 120 // 2 minutes } + +func (self *SRegion) GetVSwitches(ids []string, vpcId string, offset int, limit int) ([]SVSwitch, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + if ids != nil && len(ids) > 0 { + params["VSwitchId"] = strings.Join(ids, ",") + } + if len(vpcId) > 0 { + params["VpcId"] = vpcId + } + + body, err := self.vpcRequest("DescribeVSwitches", params) + if err != nil { + log.Errorf("GetVSwitches fail %s", err) + return nil, 0, err + } + + switches := make([]SVSwitch, 0) + err = body.Unmarshal(&switches, "VSwitches", "VSwitch") + if err != nil { + log.Errorf("Unmarshal vswitches fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + return switches, int(total), nil +} + +func (self *SRegion) GetVSwitchAttributes(idstr string) (*SVSwitch, error) { + params := make(map[string]string) + params["VSwitchId"] = idstr + + body, err := self.vpcRequest("DescribeVSwitchAttributes", params) + if err != nil { + log.Errorf("DescribeVSwitchAttributes fail %s", err) + return nil, err + } + if self.client.Debug { + log.Debugf("%s", body.PrettyString()) + } + switches := SVSwitch{} + err = body.Unmarshal(&switches) + if err != nil { + log.Errorf("Unmarshal vswitches fail %s", err) + return nil, err + } + return &switches, nil +} + +func (vsw *SVSwitch) dissociateWithSNAT() error { + natgatways, err := vsw.wire.vpc.getNatGateways() + if err != nil { + return err + } + for i := range natgatways { + err = natgatways[i].dissociateWithVswitch(vsw.VSwitchId) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/util/aliyun/zone.go b/pkg/util/aliyun/zone.go index 249554c058..00ae518797 100644 --- a/pkg/util/aliyun/zone.go +++ b/pkg/util/aliyun/zone.go @@ -152,9 +152,13 @@ func (self *SZone) GetIRegion() cloudprovider.ICloudRegion { } func (self *SZone) fetchStorages() error { - self.istorages = make([]cloudprovider.ICloudStorage, len(self.AvailableDiskCategories.DiskCategories)) + categories := self.AvailableDiskCategories.DiskCategories + // if len(self.AvailableResources.ResourcesInfo) > 0 { + // categories = self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory + // } + self.istorages = make([]cloudprovider.ICloudStorage, len(categories)) - for i, sc := range self.AvailableDiskCategories.DiskCategories { + for i, sc := range categories { storage := SStorage{zone: self, storageType: sc} self.istorages[i] = &storage } @@ -236,3 +240,10 @@ func (self *SZone) getNetworkById(vswitchId string) *SVSwitch { } return nil } + +func (self *SZone) getSysDiskCategories() []string { + if len(self.AvailableResources.ResourcesInfo) > 0 { + return self.AvailableResources.ResourcesInfo[0].SystemDiskCategories.SupportedSystemDiskCategory + } + return nil +} diff --git a/pkg/util/aws/eip.go b/pkg/util/aws/eip.go index f732258950..1e76df40fd 100644 --- a/pkg/util/aws/eip.go +++ b/pkg/util/aws/eip.go @@ -236,7 +236,7 @@ func (self *SRegion) AllocateEIP(domainType string) (*SEipAddress, error) { return self.GetEip(*eip.AllocationId) } -func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) { +func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) { // todo: aws 不支持指定bwMbps, chargeType ? log.Debugf("CreateEip: aws not support specific params name/bwMbps/chargeType.") ieip, err := self.AllocateEIP("vpc") diff --git a/pkg/util/aws/host.go b/pkg/util/aws/host.go index 2a8578b4a3..0e678087ff 100644 --- a/pkg/util/aws/host.go +++ b/pkg/util/aws/host.go @@ -8,7 +8,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/util/billing" ) type SHost struct { @@ -161,10 +160,8 @@ func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) { return inst, nil } -func (self *SHost) CreateVM(name, imgId string, sysDiskSize, cpu, memMB int, networkId, ipAddr, desc, - passwd, storageType string, diskSizes []int, publicKey string, secgroupId string, userData string, - bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - vmId, err := self._createVM(name, imgId, sysDiskSize, cpu, memMB, "", networkId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, secgroupId, userData) +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + vmId, err := self._createVM(desc.Name, desc.ExternalImageId, desc.SysDisk, desc.Cpu, desc.MemoryMB, desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, desc.DataDisks, desc.PublicKey, desc.ExternalSecgroupId, desc.UserData) if err != nil { return nil, err } @@ -177,24 +174,9 @@ func (self *SHost) CreateVM(name, imgId string, sysDiskSize, cpu, memMB int, net return vm, err } -func (self *SHost) CreateVM2(name, imgId string, sysDiskSize int, instanceType string, networkId, ipAddr, desc, - passwd, storageType string, diskSizes []int, publicKey string, secgroupId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - vmId, err := self._createVM(name, imgId, sysDiskSize, 0, 0, instanceType, networkId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, secgroupId, userData) - if err != nil { - return nil, err - } - - vm, err := self.GetInstanceById(vmId) - if err != nil { - return nil, err - } - - return vm, err -} - -func (self *SHost) _createVM(name, imgId string, sysDiskSize int, cpu, memMB int, instanceType string, - networkId, ipAddr, desc, passwd, - storageType string, diskSizes []int, publicKey string, secgroupId string, userData string) (string, error) { +func (self *SHost) _createVM(name, imgId string, sysDisk cloudprovider.SDiskInfo, cpu, memMB int, instanceType string, + networkId, ipAddr, desc, passwd string, + dataDisks []cloudprovider.SDiskInfo, publicKey string, secgroupId string, userData string) (string, error) { // 网络配置及安全组绑定 net := self.zone.getNetworkById(networkId) if net == nil { @@ -250,16 +232,16 @@ func (self *SHost) _createVM(name, imgId string, sysDiskSize int, cpu, memMB int return "", fmt.Errorf("image not ready") } - disks := make([]SDisk, len(diskSizes)+1) - disks[0].Size = img.Size - if sysDiskSize > 0 && sysDiskSize > img.Size { - disks[0].Size = sysDiskSize + disks := make([]SDisk, len(dataDisks)+1) + disks[0].Size = img.SizeGB + if sysDisk.SizeGB > 0 && sysDisk.SizeGB > img.SizeGB { + disks[0].Size = sysDisk.SizeGB } - disks[0].Category = storageType + disks[0].Category = sysDisk.StorageType - for i, sz := range diskSizes { - disks[i+1].Size = sz - disks[i+1].Category = storageType + for i, dataDisk := range dataDisks { + disks[i+1].Size = dataDisk.SizeGB + disks[i+1].Category = dataDisk.StorageType } // 创建实例 diff --git a/pkg/util/aws/image.go b/pkg/util/aws/image.go index 416475687c..516740c2e2 100644 --- a/pkg/util/aws/image.go +++ b/pkg/util/aws/image.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "strings" + "time" "github.com/aws/aws-sdk-go/service/ec2" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -22,13 +24,19 @@ const ( ImageStatusCreateFailed ImageStatusType = "failed" ) -type ImageOwnerType string +type TImageOwnerType string const ( - ImageOwnerSystem ImageOwnerType = "amazon" - ImageOwnerSelf ImageOwnerType = "self" - ImageOwnerOthers ImageOwnerType = "microsoft" - ImageOwnerMarketplace ImageOwnerType = "aws-marketplace" + ImageOwnerTypeSystem = TImageOwnerType("system") + ImageOwnerTypeSelf = TImageOwnerType("self") + ImageOwnerTypeOther = TImageOwnerType("other") +) + +var ( + ImageOwnerAll = []TImageOwnerType(nil) + ImageOwnerSelf = []TImageOwnerType{ImageOwnerTypeSelf} + ImageOwnerSystem = []TImageOwnerType{ImageOwnerTypeSystem} + ImageOwnerSelfSystem = []TImageOwnerType{ImageOwnerTypeSystem, ImageOwnerTypeSelf} ) type ImageImportTask struct { @@ -46,21 +54,32 @@ type RootDevice struct { type SImage struct { storageCache *SStoragecache - Architecture string - CreationTime string - Description string - ImageId string - ImageName string - OSName string - OSType string - ImageType string - IsSupportCloudinit bool - IsSupportIoOptimized bool - Platform string - Size int - Status ImageStatusType - Usage string - RootDevice RootDevice + Architecture string + CreationTime time.Time + Description string + ImageId string + ImageName string + OSType string + ImageType string + // IsSupportCloudinit bool + EnaSupport bool + Platform string + SizeGB int + Status ImageStatusType + OwnerType string + // Usage string + RootDevice RootDevice + + Public bool + Hypervisor string + VirtualizationType string + OwnerId string + + ProductCodes []*ec2.ProductCode + + OSVersion string + OSDist string + OSBuildId string } func (self *SImage) GetId() string { @@ -78,13 +97,26 @@ func (self *SImage) GetGlobalId() string { func (self *SImage) GetStatus() string { switch self.Status { case ImageStatusCreating: - return models.IMAGE_STATUS_QUEUED + return models.CACHED_IMAGE_STATUS_CACHING case ImageStatusAvailable: - return models.IMAGE_STATUS_ACTIVE + return models.CACHED_IMAGE_STATUS_READY case ImageStatusCreateFailed: - return models.IMAGE_STATUS_KILLED + return models.CACHED_IMAGE_STATUS_CACHE_FAILED default: - return models.IMAGE_STATUS_KILLED + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (self *SImage) GetImageStatus() string { + switch self.Status { + case ImageStatusCreating: + return cloudprovider.IMAGE_STATUS_QUEUED + case ImageStatusAvailable: + return cloudprovider.IMAGE_STATUS_ACTIVE + case ImageStatusCreateFailed: + return cloudprovider.IMAGE_STATUS_KILLED + default: + return cloudprovider.IMAGE_STATUS_KILLED } } @@ -96,6 +128,42 @@ func (self *SImage) Refresh() error { return jsonutils.Update(self, new) } +func (self *SImage) GetImageType() string { + return self.ImageType +} + +func (self *SImage) GetSize() int64 { + return int64(self.SizeGB) * 1024 * 1024 * 1024 +} + +func (self *SImage) GetOsType() string { + return self.OSType +} + +func (self *SImage) GetOsArch() string { + return self.Architecture +} + +func (self *SImage) GetOsDist() string { + return self.OSDist +} + +func (self *SImage) GetOsVersion() string { + return self.OSVersion +} + +func (self *SImage) GetMinOsDiskSizeGb() int { + return self.SizeGB +} + +func (self *SImage) GetImageFormat() string { + return "vhd" +} + +func (self *SImage) GetCreateTime() time.Time { + return self.CreationTime +} + func (self *SImage) IsEmulated() bool { return false } @@ -108,11 +176,11 @@ func (self *SImage) GetMetadata() *jsonutils.JSONDict { if len(self.OSType) > 0 { data.Add(jsonutils.NewString(self.OSType), "os_name") } - if len(self.Platform) > 0 { - data.Add(jsonutils.NewString(self.Platform), "os_distribution") + if len(self.OSDist) > 0 { + data.Add(jsonutils.NewString(self.OSDist), "os_distribution") } - if len(self.OSName) > 0 { - data.Add(jsonutils.NewString(self.OSName), "os_version") + if len(self.OSVersion) > 0 { + data.Add(jsonutils.NewString(self.OSVersion), "os_version") } return data } @@ -178,22 +246,22 @@ func (self *SRegion) GetImage(imageId string) (*SImage, error) { return nil, fmt.Errorf("GetImage image id should not be empty") } - images, _, err := self.GetImages("", ImageOwnerType(""), []string{imageId}, "", 0, 1) + images, err := self.getImages("", ImageOwnerAll, []string{imageId}, "", "", nil, "") if err != nil { return nil, err } if len(images) == 0 { - return nil, fmt.Errorf("image %s not found", imageId) + return nil, cloudprovider.ErrNotFound } return &images[0], nil } -func (self *SRegion) GetImageByName(name string) (*SImage, error) { +func (self *SRegion) GetImageByName(name string, owners []TImageOwnerType) (*SImage, error) { if len(name) == 0 { return nil, fmt.Errorf("image name should not be empty") } - images, _, err := self.GetImages("", ImageOwnerType(""), nil, name, 0, 1) + images, err := self.getImages("", owners, nil, name, "hvm", nil, "") if err != nil { return nil, err } @@ -216,15 +284,53 @@ func (self *SRegion) GetImageStatus(imageId string) (ImageStatusType, error) { func getRootDiskSize(image *ec2.Image) (int, error) { rootDeivce := *image.RootDeviceName for _, volume := range image.BlockDeviceMappings { - if len(rootDeivce) > 0 && *volume.DeviceName == rootDeivce { + if len(rootDeivce) > 0 && *volume.DeviceName == rootDeivce && volume.Ebs != nil && volume.Ebs.VolumeSize != nil { return int(*volume.Ebs.VolumeSize), nil } } - return 0, fmt.Errorf("image size not found: %s", image.String()) } -func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, imageId []string, name string, offset int, limit int) ([]SImage, int, error) { +func getLatestImage(images []SImage) SImage { + var latestBuild string + latestBuildIdx := -1 + for i := range images { + if latestBuildIdx < 0 || comapreImageBuildIds(latestBuild, images[i]) < 0 { + latestBuild = images[i].OSBuildId + latestBuildIdx = i + } + } + return images[latestBuildIdx] +} + +func (self *SRegion) GetImages(status ImageStatusType, owners []TImageOwnerType, imageId []string, name string, virtualizationType string, ownerIds []string, volumeType string, latest bool) ([]SImage, error) { + images, err := self.getImages(status, owners, imageId, name, virtualizationType, ownerIds, volumeType) + if err != nil { + return nil, err + } + if !latest { + return images, err + } + noVersionImages := make([]SImage, 0) + versionedImages := make(map[string][]SImage) + for i := range images { + key := fmt.Sprintf("%s%s", images[i].OSDist, images[i].OSVersion) + if len(key) == 0 { + noVersionImages = append(noVersionImages, images[i]) + continue + } + if _, ok := versionedImages[key]; !ok { + versionedImages[key] = make([]SImage, 0) + } + versionedImages[key] = append(versionedImages[key], images[i]) + } + for key := range versionedImages { + noVersionImages = append(noVersionImages, getLatestImage(versionedImages[key])) + } + return noVersionImages, nil +} + +func (self *SRegion) getImages(status ImageStatusType, owners []TImageOwnerType, imageId []string, name string, virtualizationType string, ownerIds []string, volumeType string) ([]SImage, error) { params := &ec2.DescribeImagesInput{} filters := make([]*ec2.Filter, 0) if len(status) > 0 { @@ -235,9 +341,18 @@ func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, ima filters = AppendSingleValueFilter(filters, "name", name) } - if len(owner) > 0 { - own := string(owner) - params.SetOwners([]*string{&own}) + if len(virtualizationType) > 0 { + filters = AppendSingleValueFilter(filters, "virtualization-type", virtualizationType) + } + + if len(volumeType) > 0 { + filters = AppendSingleValueFilter(filters, "block-device-mapping.volume-type", volumeType) + } + + filters = AppendSingleValueFilter(filters, "image-type", "machine") + + if len(owners) > 0 || len(ownerIds) > 0 { + params.SetOwners(imageOwnerTypes2Strings(owners, ownerIds)) } if len(imageId) > 0 { @@ -251,15 +366,17 @@ func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, ima ret, err := self.ec2Client.DescribeImages(params) if err != nil { if strings.Contains(err.Error(), ".NotFound") { - return nil, 0, cloudprovider.ErrNotFound + return nil, cloudprovider.ErrNotFound } - return nil, 0, err + return nil, err } images := []SImage{} - for _, image := range ret.Images { + for i := range ret.Images { + image := ret.Images[i] + if err := FillZero(image); err != nil { - return nil, 0, err + return nil, err } tagspec := TagSpec{} @@ -267,7 +384,9 @@ func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, ima size, err := getRootDiskSize(image) if err != nil { - log.Debugf(err.Error()) + // fail to get disk size, ignore the image + /// log.Debugf(err.Error()) + continue } var rootDevice RootDevice @@ -286,25 +405,43 @@ func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, ima osType = "Windows" } - images = append(images, SImage{ - storageCache: self.getStoragecache(), - Architecture: *image.Architecture, - Description: *image.Description, - ImageId: *image.ImageId, - ImageName: tagspec.GetNameTag(), - ImageType: *image.ImageType, - IsSupportIoOptimized: *image.EnaSupport, - Platform: *image.Platform, - Status: ImageStatusType(*image.State), - CreationTime: *image.CreationDate, - Size: size, - RootDevice: rootDevice, - OSType: osType, - // Usage: "", - }) + createTime, _ := timeutils.ParseTimeStr(*image.CreationDate) + + name := tagspec.GetNameTag() + if len(name) == 0 && image.Name != nil { + name = *image.Name + } + + sImage := SImage{ + storageCache: self.getStoragecache(), + Architecture: *image.Architecture, + Description: *image.Description, + ImageId: *image.ImageId, + Public: *image.Public, + ImageName: name, + OSType: osType, + // ImageType: *image.ImageType, + OwnerType: *image.ImageOwnerAlias, + EnaSupport: *image.EnaSupport, + Platform: *image.Platform, + Status: ImageStatusType(*image.State), + CreationTime: createTime, + SizeGB: size, + RootDevice: rootDevice, + VirtualizationType: *image.VirtualizationType, + Hypervisor: *image.Hypervisor, + ProductCodes: image.ProductCodes, + OwnerId: *image.OwnerId, + } + sImage.ImageType = getImageType(sImage) + sImage.OSType = getImageOSType(sImage) + sImage.OSDist = getImageOSDist(sImage) + sImage.OSVersion = getImageOSVersion(sImage) + sImage.OSBuildId = getImageOSBuildID(sImage) + images = append(images, sImage) } - return images, len(images), nil + return images, nil } func (self *SRegion) DeleteImage(imageId string) error { @@ -313,3 +450,17 @@ func (self *SRegion) DeleteImage(imageId string) error { _, err := self.ec2Client.DeregisterImage(params) return err } + +func (self *SRegion) addTags(resId string, key string, value string) error { + input := &ec2.CreateTagsInput{} + input.SetResources([]*string{&resId}) + tag := ec2.Tag{} + tag.Key = &key + tag.Value = &value + input.SetTags([]*ec2.Tag{&tag}) + _, err := self.ec2Client.CreateTags(input) + if err != nil { + return err + } + return nil +} diff --git a/pkg/util/aws/imageowners.go b/pkg/util/aws/imageowners.go new file mode 100644 index 0000000000..8fbe98c5d2 --- /dev/null +++ b/pkg/util/aws/imageowners.go @@ -0,0 +1,572 @@ +package aws + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "yunion.io/x/pkg/util/regutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +func getSystemOwnerIds() []string { + keys := make([]string, len(awsImagePublishers)) + idx := 0 + for key := range awsImagePublishers { + keys[idx] = key + idx += 1 + } + return keys +} + +func stringArray2StringPtrArray(arr []string) []*string { + ret := make([]*string, len(arr)) + for i := range arr { + ret[i] = &arr[i] + } + return ret +} + +func imageOwnerTypes2Strings(owners []TImageOwnerType, rawIds []string) []*string { + ownerIds := make([]string, 0) + for i := range owners { + switch owners[i] { + case ImageOwnerTypeSelf: + ownerIds = append(ownerIds, "self") + case ImageOwnerTypeSystem: + sysOwnerIds := getSystemOwnerIds() + ownerIds = append(ownerIds, sysOwnerIds...) + } + } + ownerIds = append(ownerIds, rawIds...) + return stringArray2StringPtrArray(ownerIds) +} + +type SAWSImagePublisherInfo struct { + GetOSType func(image SImage) string + GetOSDist func(image SImage) string + GetOSVersion func(image SImage) string + GetOSBuildID func(image SImage) string + CompareBuilds func(v1, v2 string) int +} + +var rhel = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + return "RHEL" + }, + GetOSVersion: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + if len(parts) >= 2 { + parts = strings.Split(parts[1], "_") + return parts[0] + } + return "" + }, + GetOSBuildID: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + if len(parts) >= 2 { + return parts[2] + } + return "" + }, +} + +var ( + debianDatePattern = regexp.MustCompile(`-\d{4}-\d{2}-\d{2}-`) +) + +var debian = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + return "Debian" + }, + GetOSVersion: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + if len(parts) >= 5 { + return parts[1] + } + return "" + }, + GetOSBuildID: func(image SImage) string { + dateStr := debianDatePattern.FindString(image.ImageName) + if len(dateStr) > 2 { + return dateStr[1 : len(dateStr)-1] + } + return "" + }, +} + +var ( + centosDatePattern = regexp.MustCompile(`\s+\d{4,8}(_\d+)?`) +) + +var centos = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + if strings.Index(image.ImageName, "Atomic") > 0 { + return "CentOS Atomic" + } else { + return "CentOS" + } + }, + GetOSVersion: func(image SImage) string { + parts := strings.Split(image.ImageName, " ") + if strings.Index(image.ImageName, "Atomic") > 0 { + if regutils.MatchInteger(parts[3]) { + return parts[3] + } else { + return "7" + } + } else if strings.HasPrefix(image.ImageName, "CentOS Linux ") { + return parts[2] + } else { + return parts[1] + } + return "" + }, + GetOSBuildID: func(image SImage) string { + build := centosDatePattern.FindString(image.ImageName) + build = strings.TrimSpace(build) + if strings.HasPrefix(build, "201") { + build = build[2:] + } + return build + }, +} + +var ubuntuReleases = map[string]string{ + "warty": "4.10", + "hoary": "5.04", + "breezy": "5.10", + "dapper": "6.06", + "edgy": "6.10", + "feisty": "7.04", + "gutsy": "7.10", + "hardy": "8.04", + "intrepid": "8.10", + "jaunty": "9.04", + "karmic": "9.10", + "lucid": "10.04", + "maverick": "10.10", + "natty": "11.04", + "oneiric": "11.10", + "precise": "12.04", + "quantal": "12.10", + "raring": "13.04", + "saucy": "13.10", + "trusty": "14.04", + "utopic": "14.10", + "vivid": "15.04", + "wily": "15.10", + "xenial": "16.04", + "yakkety": "16.10", + "zesty": "17.04", + "artful": "17.10", + "bionic": "18.04", + "cosmic": "18.10", + "disco": "19.04", +} + +var ubuntuReleasePattern = regexp.MustCompile(`-\d+\.\d+-`) + +var ubuntu = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + if strings.HasPrefix(image.ImageName, "ubuntu-minimal/") { + return "Ubuntu Minimal" + } + if strings.HasPrefix(image.ImageName, "ubuntu/") { + return "Ubuntu" + } + if strings.HasPrefix(image.ImageName, "ubuntu-rolling-") || strings.HasPrefix(image.ImageName, "ubuntu-core") || strings.Index(image.ImageName, "core-edge") > 0 { + return "Ubuntu Core" + } + return "Ubuntu" + }, + GetOSVersion: func(image SImage) string { + relStr := ubuntuReleasePattern.FindString(image.ImageName) + if len(relStr) > 2 { + return relStr[1 : len(relStr)-1] + } + parts := strings.Split(image.ImageName, "/") + if len(parts) >= 4 { + parts = strings.Split(parts[3], "-") + if len(parts) >= 2 { + relName := strings.ToLower(parts[1]) + if _, ok := ubuntuReleases[relName]; ok { + return ubuntuReleases[relName] + } else { + return relName + } + } + } + if strings.HasPrefix(image.ImageName, "ubuntu-rolling-") { + parts := strings.Split(image.ImageName, "-") + if len(parts) > 3 { + return strings.ToLower(parts[2]) + } + } + return "" + }, + GetOSBuildID: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + return parts[len(parts)-1] + }, +} + +var ( + SUSE_SLES = regexp.MustCompile(`suse-sles-\d+-v?\d+-`) + SUSE_SLES_SP = regexp.MustCompile(`suse-sles-\d+-sp\d+-v?\d+-`) + SUSE_SLES_RIGHTLINK = regexp.MustCompile(`suse-sles-\d+-rightscale-v?\d+-`) + SUSE_SLES_RIGHTLINK_SP = regexp.MustCompile(`suse-sles-\d+-sp\d+-rightscale-v?\d+-`) + SUSE_SLES_SAPCAL = regexp.MustCompile(`suse-sles-\d+-sapcal-v?\d+-`) + SUSE_SLES_SAPCAL_SP = regexp.MustCompile(`suse-sles-\d+-sp\d+-sapcal-v?\d+-`) + SUSE_SLES_BYOS = regexp.MustCompile(`suse-sles-\d+-byos-v?\d+-`) + SUSE_SLES_BYOS_SP = regexp.MustCompile(`suse-sles-\d+-sp\d+-byos-v?\d+-`) + SUSE_SLES_SAP = regexp.MustCompile(`suse-sles-sap-\d+-v\d+-`) + SUSE_SLES_SAP_SP = regexp.MustCompile(`suse-sles-sap-\d+-sp\d+-v\d+-`) + SUSE_SLES_SAP_BYOS = regexp.MustCompile(`suse-sles-sap-\d+-byos-v?\d+-`) + SUSE_SLES_SAP_BYOS_SP = regexp.MustCompile(`suse-sles-sap-\d+-sp\d+-byos-v?\d+-`) + + SUSE_CAASP_CLUSTER_BYOS = regexp.MustCompile(`suse-caasp-\d+-\d+-cluster-byos-v?\d+-`) + SUSE_CAASP_ADMIN_BYOS = regexp.MustCompile(`suse-caasp-\d+-\d+-admin-byos-v?\d+-`) + + SUSE_MANAGER_SERVER_BYOS = regexp.MustCompile(`suse-manager-\d+-\d+-server-byos-v?\d+-`) + SUSE_MANAGER_PROXY_BYOS = regexp.MustCompile(`suse-manager-\d+-\d+-proxy-byos-v?\d+-`) +) + +func getBuildId(ver string) string { + if ver[0] == 'v' { + return ver[1:] + } else { + return ver + } +} + +var suse = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + switch { + case SUSE_SLES.MatchString(image.ImageName), SUSE_SLES_SP.MatchString(image.ImageName): + return "SUSE Linux Enterpise Server" + case SUSE_SLES_RIGHTLINK.MatchString(image.ImageName), SUSE_SLES_RIGHTLINK_SP.MatchString(image.ImageName): + return "SUSE Linux Enterpise Server with RightLink" + case SUSE_SLES_SAPCAL.MatchString(image.ImageName), SUSE_SLES_SAPCAL_SP.MatchString(image.ImageName): + return "SUSE Linux Enterpise Server for SAP CAL" + case SUSE_SLES_BYOS.MatchString(image.ImageName), SUSE_SLES_BYOS_SP.MatchString(image.ImageName): + return "SUSE Linux Enterpise Server BYOS" + case SUSE_SLES_SAP.MatchString(image.ImageName), SUSE_SLES_SAP_SP.MatchString(image.ImageName): + return "SUSE Linux Enterpise Server for SAP Application" + case SUSE_SLES_SAP_BYOS.MatchString(image.ImageName), SUSE_SLES_SAP_BYOS_SP.MatchString(image.ImageName): + return "SUSE Linux Enterpise Server for SAP Application BYOS" + case SUSE_CAASP_CLUSTER_BYOS.MatchString(image.ImageName): + return "SUSE CaaSP Cluster Node" + case SUSE_CAASP_ADMIN_BYOS.MatchString(image.ImageName): + return "SUSE CaaSP Admin Node" + case SUSE_MANAGER_SERVER_BYOS.MatchString(image.ImageName): + return "SUSE Manager Server" + case SUSE_MANAGER_PROXY_BYOS.MatchString(image.ImageName): + return "SUSE Manager Proxy" + } + return "SUSE" + }, + GetOSVersion: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + switch { + case SUSE_SLES.MatchString(image.ImageName): + return parts[2] + case SUSE_SLES_SP.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3][2:]) + case SUSE_SLES_RIGHTLINK.MatchString(image.ImageName): + return parts[2] + case SUSE_SLES_RIGHTLINK_SP.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3][2:]) + case SUSE_SLES_SAPCAL.MatchString(image.ImageName): + return parts[2] + case SUSE_SLES_SAPCAL_SP.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3][2:]) + case SUSE_SLES_BYOS.MatchString(image.ImageName): + return parts[2] + case SUSE_SLES_BYOS_SP.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3][2:]) + case SUSE_SLES_SAP.MatchString(image.ImageName): + return parts[3] + case SUSE_SLES_SAP_SP.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[3], parts[4][2:]) + case SUSE_SLES_SAP_BYOS.MatchString(image.ImageName): + return parts[3] + case SUSE_SLES_SAP_BYOS_SP.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[3], parts[4][2:]) + case SUSE_CAASP_CLUSTER_BYOS.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3]) + case SUSE_CAASP_ADMIN_BYOS.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3]) + case SUSE_MANAGER_SERVER_BYOS.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3]) + case SUSE_MANAGER_PROXY_BYOS.MatchString(image.ImageName): + return fmt.Sprintf("%s.%s", parts[2], parts[3]) + } + return "" + }, + GetOSBuildID: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + switch { + case SUSE_SLES.MatchString(image.ImageName): + return getBuildId(parts[3]) + case SUSE_SLES_SP.MatchString(image.ImageName): + return getBuildId(parts[4]) + case SUSE_SLES_RIGHTLINK.MatchString(image.ImageName): + return getBuildId(parts[4]) + case SUSE_SLES_RIGHTLINK_SP.MatchString(image.ImageName): + return getBuildId(parts[5]) + case SUSE_SLES_SAPCAL.MatchString(image.ImageName): + return getBuildId(parts[4]) + case SUSE_SLES_SAPCAL_SP.MatchString(image.ImageName): + return getBuildId(parts[5]) + case SUSE_SLES_BYOS.MatchString(image.ImageName): + return getBuildId(parts[4]) + case SUSE_SLES_BYOS_SP.MatchString(image.ImageName): + return getBuildId(parts[5]) + case SUSE_SLES_SAP.MatchString(image.ImageName): + return getBuildId(parts[4]) + case SUSE_SLES_SAP_SP.MatchString(image.ImageName): + return getBuildId(parts[5]) + case SUSE_SLES_SAP_BYOS.MatchString(image.ImageName): + return getBuildId(parts[5]) + case SUSE_SLES_SAP_BYOS_SP.MatchString(image.ImageName): + return getBuildId(parts[6]) + case SUSE_CAASP_CLUSTER_BYOS.MatchString(image.ImageName): + return getBuildId(parts[6]) + case SUSE_CAASP_ADMIN_BYOS.MatchString(image.ImageName): + return getBuildId(parts[6]) + case SUSE_MANAGER_SERVER_BYOS.MatchString(image.ImageName): + return getBuildId(parts[6]) + case SUSE_MANAGER_PROXY_BYOS.MatchString(image.ImageName): + return getBuildId(parts[6]) + } + return "" + }, +} + +var coreosVersionTable = map[string]int{ + "alpha": 0, + "beta": 1, + "stable": 2, +} + +var coreos = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + return "CoreOS" + }, + GetOSVersion: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + subparts := strings.Split(parts[2], ".") + return subparts[0] + }, + GetOSBuildID: func(image SImage) string { + parts := strings.Split(image.ImageName, "-") + return fmt.Sprintf("%s-%s", parts[1], parts[2]) + }, + CompareBuilds: func(v1, v2 string) int { + parts1 := strings.Split(v1, "-") + parts2 := strings.Split(v2, "-") + majorV1 := coreosVersionTable[parts1[0]] + majorV2 := coreosVersionTable[parts2[0]] + if majorV1 != majorV2 { + return majorV1 - majorV2 + } + parts1 = strings.Split(parts1[1], ".") + parts2 = strings.Split(parts2[1], ".") + for i := 0; i < len(parts1) && i < len(parts2); i += 1 { + n1, _ := strconv.Atoi(parts1[i]) + n2, _ := strconv.Atoi(parts2[i]) + if n1 != n2 { + return n1 - n2 + } + } + return len(parts1) - len(parts2) + }, +} + +var ( + sqlServerPattern = regexp.MustCompile(`-SQL_(\d+)_(\w+)-`) + dotnetcorePattern = regexp.MustCompile(`-dotnetcore-`) +) + +var windowsServer = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + if strings.HasPrefix(image.ImageName, "ubuntu-") || strings.HasPrefix(image.ImageName, "amzn-ami-") || strings.HasPrefix(image.ImageName, "amzn2-ami-") { + return "Linux" + } else { + return "Windows" + } + }, + GetOSDist: func(image SImage) string { + osStr := "Windows Server" + if strings.HasPrefix(image.ImageName, "ubuntu-") { + osStr = "Ubuntu" + } else if strings.HasPrefix(image.ImageName, "amzn-ami-") || strings.HasPrefix(image.ImageName, "amzn2-ami-") { + osStr = "Amazon Linux" + } + apps := make([]string, 0) + matchApp := sqlServerPattern.FindStringSubmatch(image.ImageName) + if len(matchApp) > 0 { + apps = append(apps, fmt.Sprintf("SQL Server %s %s", matchApp[1], matchApp[2])) + } + if dotnetcorePattern.MatchString(image.ImageName) { + apps = append(apps, ".Net Core") + } + if len(apps) > 0 { + osStr = fmt.Sprintf("%s with %s", osStr, strings.Join(apps, " ")) + } + return osStr + }, + GetOSVersion: func(image SImage) string { + if strings.HasPrefix(image.ImageName, "ubuntu-") { + return ubuntu.GetOSVersion(image) + } + if strings.HasPrefix(image.ImageName, "amzn-ami-") || strings.HasPrefix(image.ImageName, "amzn2-ami-") { + return amazon.GetOSVersion(image) + } + parts := strings.Split(image.ImageName, "-") + return strings.Join(parts[1:len(parts)-1], " ") + }, + GetOSBuildID: func(image SImage) string { + if strings.HasPrefix(image.ImageName, "ubuntu-") { + return ubuntu.GetOSBuildID(image) + } + if strings.HasPrefix(image.ImageName, "amzn-ami-") || strings.HasPrefix(image.ImageName, "amzn2-ami-") { + return amazon.GetOSBuildID(image) + } + parts := strings.Split(image.ImageName, "-") + return parts[len(parts)-1] + }, +} + +var ( + amazonVersionPattern = regexp.MustCompile(`-(\d{4})\.(\d{2})\.(rc-\d+|\d+)(\.(\d+))?`) + amazonVersionPattern2 = regexp.MustCompile(`-(\d{1,2})\.(\d{1,2})\.(\d{8})(\.(\d+))?`) +) + +var amazon = SAWSImagePublisherInfo{ + GetOSType: func(image SImage) string { + return "Linux" + }, + GetOSDist: func(image SImage) string { + if strings.HasPrefix(image.ImageName, "amzn-ami-minimal-") || strings.HasPrefix(image.ImageName, "amzn2-ami-minimal-") { + return "Amazon Linux Minimal" + } else { + return "Amazon Linux" + } + }, + GetOSVersion: func(image SImage) string { + verStrs := amazonVersionPattern2.FindStringSubmatch(image.ImageName) + if len(verStrs) > 3 { + return fmt.Sprintf("%s.%s.%s", verStrs[1], verStrs[2], verStrs[3][:6]) + } + verStrs = amazonVersionPattern.FindStringSubmatch(image.ImageName) + if len(verStrs) > 3 { + return fmt.Sprintf("%s.%s.%s", verStrs[1], verStrs[2], verStrs[3]) + } + return "" + }, + GetOSBuildID: func(image SImage) string { + verStrs := amazonVersionPattern2.FindStringSubmatch(image.ImageName) + if len(verStrs) > 5 && len(verStrs[5]) > 0 { + return fmt.Sprintf("%s.%s", verStrs[3], verStrs[5]) + } else if len(verStrs) > 3 { + return verStrs[3] + } + verStrs = amazonVersionPattern.FindStringSubmatch(image.ImageName) + if len(verStrs) > 5 { + return verStrs[5] + } + return "" + }, +} + +var awsImagePublishers = map[string]SAWSImagePublisherInfo{ + "841258680906": rhel, // china + "309956199498": rhel, // international + "673060587306": debian, // china + "379101102735": debian, // international + "718707510307": centos, // china + "410186602215": centos, // international + "837727238323": ubuntu, // china + "099720109477": ubuntu, // internaltional + "841869936221": suse, // china + "013907871322": suse, // international + "280032941352": coreos, // china + "595879546273": coreos, // international + "016951021795": windowsServer, // china + "801119661308": windowsServer, // international + "141808717104": amazon, // china + "137112412989": amazon, // international +} + +func getImageOSType(image SImage) string { + ownerInfo, ok := awsImagePublishers[image.OwnerId] + if ok { + return ownerInfo.GetOSType(image) + } + return image.OSType +} + +func getImageOSDist(image SImage) string { + ownerInfo, ok := awsImagePublishers[image.OwnerId] + if ok { + return ownerInfo.GetOSDist(image) + } + return "" +} + +func getImageOSVersion(image SImage) string { + ownerInfo, ok := awsImagePublishers[image.OwnerId] + if ok { + return ownerInfo.GetOSVersion(image) + } + return "" +} + +func getImageOSBuildID(image SImage) string { + ownerInfo, ok := awsImagePublishers[image.OwnerId] + if ok { + return ownerInfo.GetOSBuildID(image) + } + return "" +} + +func comapreImageBuildIds(ver1 string, img2 SImage) int { + ownerInfo, ok := awsImagePublishers[img2.OwnerId] + if ok && ownerInfo.CompareBuilds != nil { + return ownerInfo.CompareBuilds(ver1, img2.OSBuildId) + } + return strings.Compare(ver1, img2.OSBuildId) +} + +func getImageType(image SImage) string { + _, ok := awsImagePublishers[image.OwnerId] + if ok { + return cloudprovider.CachedImageTypeSystem + } + if !image.Public { + return cloudprovider.CachedImageTypeCustomized + } + return cloudprovider.CachedImageTypeMarket +} diff --git a/pkg/util/aws/imageowners_test.go b/pkg/util/aws/imageowners_test.go new file mode 100644 index 0000000000..0f83aa34aa --- /dev/null +++ b/pkg/util/aws/imageowners_test.go @@ -0,0 +1,35 @@ +package aws + +import ( + "regexp" + "testing" +) + +func TestImageDistMatch(t *testing.T) { + cases := []struct { + pattern *regexp.Regexp + match string + }{ + {SUSE_SLES, "suse-sles-11-v20161021-hvm-ssd-x86_64"}, + {SUSE_SLES_SP, "suse-sles-11-sp4-v20161021-hvm-ssd-x86_64"}, + {SUSE_SLES_RIGHTLINK, "suse-sles-11-rightscale-v20160804-hvm-ssd-x86_64"}, + {SUSE_SLES_RIGHTLINK_SP, "suse-sles-11-sp4-rightscale-v20160804-hvm-ssd-x86_64"}, + {SUSE_SLES_SAPCAL, "suse-sles-11-sapcal-20150729-hvm-mag-x86_64"}, + {SUSE_SLES_SAPCAL_SP, "suse-sles-11-sp4-sapcal-20150729-hvm-mag-x86_64"}, + {SUSE_SLES_BYOS, "suse-sles-15-byos-v20180806-hvm-ssd-x86_64"}, + {SUSE_SLES_BYOS_SP, "suse-sles-15-sp4-byos-v20180806-hvm-ssd-x86_64"}, + {SUSE_SLES_SAP, "suse-sles-sap-12-v20180706-hvm-ssd-x86_64"}, + {SUSE_SLES_SAP_SP, "suse-sles-sap-12-sp2-v20180706-hvm-ssd-x86_64"}, + {SUSE_SLES_SAP_BYOS, "suse-sles-sap-12-byos-v20180706-hvm-ssd-x86_64"}, + {SUSE_SLES_SAP_BYOS_SP, "suse-sles-sap-12-sp2-byos-v20180706-hvm-ssd-x86_64"}, + {SUSE_CAASP_CLUSTER_BYOS, "suse-caasp-2-1-cluster-byos-v20180815-hvm-ssd-x86_64"}, + {SUSE_CAASP_ADMIN_BYOS, "suse-caasp-2-1-admin-byos-v20180524-hvm-ssd-x86_64"}, + {SUSE_MANAGER_SERVER_BYOS, "suse-manager-3-1-server-byos-v20170627-hvm-ssd-x86_64"}, + {SUSE_MANAGER_PROXY_BYOS, "suse-manager-3-1-proxy-byos-v20180215-hvm-ssd-x86_64"}, + } + for _, c := range cases { + if !c.pattern.MatchString(c.match) { + t.Errorf("not match %s %s", c.pattern, c.match) + } + } +} diff --git a/pkg/util/aws/instance.go b/pkg/util/aws/instance.go index 9ba780086d..5a0e73a4fd 100644 --- a/pkg/util/aws/instance.go +++ b/pkg/util/aws/instance.go @@ -168,9 +168,12 @@ func (self *SInstance) GetMetadata() *jsonutils.JSONDict { data.Add(jsonutils.NewString(self.host.zone.GetGlobalId()), "zone_ext_id") if len(self.ImageId) > 0 { - if image, err := self.host.zone.region.GetImage(self.ImageId); err != nil { + image, err := self.host.zone.region.GetImage(self.ImageId) + if err != nil { log.Errorf("Failed to find image %s for instance %s zone %s", self.ImageId, self.GetId(), self.ZoneId) - } else if meta := image.GetMetadata(); meta != nil { + } + meta := image.GetMetadata() + if meta != nil { data.Update(meta) } } @@ -528,7 +531,7 @@ func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit NetworkInterfaces: networkInterfaces, VpcAttributes: vpcattr, ProductCodes: productCodes, - OSName: image.OSName, // todo: 这里在model层回写OSName信息 + OSName: image.OSType, // todo: 这里在model层回写OSName信息 OSType: image.OSType, // ExpiredTime: // VlanId: diff --git a/pkg/util/aws/latitude_and_longitude.go b/pkg/util/aws/latitude_and_longitude.go index 30ab4cb767..08dde55430 100644 --- a/pkg/util/aws/latitude_and_longitude.go +++ b/pkg/util/aws/latitude_and_longitude.go @@ -2,24 +2,27 @@ package aws import "yunion.io/x/onecloud/pkg/cloudprovider" +// https://docs.aws.amazon.com/general/latest/gr/rande.html + var LatitudeAndLongitude = map[string]cloudprovider.SGeographicInfo{ - "ap-south-1": {Latitude: 19.0759837, Longitude: 72.8776559, City: "", CountryCode: ""}, - "ap-northeast-3": {Latitude: 34.6937378, Longitude: 135.5021651, City: "", CountryCode: ""}, - "us-east-1": {Latitude: 37.4315734, Longitude: -78.6568942, City: "", CountryCode: ""}, - "us-east-2": {Latitude: 40.4172871, Longitude: -82.90712300000001, City: "", CountryCode: ""}, - "ap-southeast-2": {Latitude: -33.8688197, Longitude: 151.2092955, City: "", CountryCode: ""}, - "cn-northwest-1": {Latitude: 37.198731, Longitude: 106.1580937, City: "", CountryCode: "CN"}, - "eu-west-1": {Latitude: 53.41291, Longitude: -8.24389, City: "", CountryCode: ""}, - "eu-central-1": {Latitude: 50.1109221, Longitude: 8.6821267, City: "", CountryCode: ""}, - "sa-east-1": {Latitude: -23.5505199, Longitude: -46.63330939999999, City: "", CountryCode: ""}, - "ap-southeast-1": {Latitude: 1.352083, Longitude: 103.819836, City: "", CountryCode: ""}, - "ca-central-1": {Latitude: 56.130366, Longitude: -106.346771, City: "", CountryCode: ""}, - "ap-northeast-2": {Latitude: 37.566535, Longitude: 126.9779692, City: "", CountryCode: ""}, - "us-west-2": {Latitude: 43.8041334, Longitude: -120.5542012, City: "", CountryCode: ""}, - "us-gov-west-1": {Latitude: 37.09024, Longitude: -95.712891, City: "", CountryCode: ""}, - "us-west-1": {Latitude: 38.8375215, Longitude: -120.8958242, City: "", CountryCode: ""}, - "cn-north-1": {Latitude: 39.90419989999999, Longitude: 116.4073963, City: "", CountryCode: ""}, - "ap-northeast-1": {Latitude: 35.7090259, Longitude: 139.7319925, City: "", CountryCode: ""}, - "eu-west-2": {Latitude: 51.5073509, Longitude: -0.1277583, City: "", CountryCode: ""}, - "eu-west-3": {Latitude: 48.856614, Longitude: 2.3522219, City: "", CountryCode: ""}, + "us-east-2": {Latitude: 40.4172871, Longitude: -82.90712300000001, City: "Ohio", CountryCode: "US"}, + "us-east-1": {Latitude: 37.4315734, Longitude: -78.6568942, City: "N. Virginia", CountryCode: "US"}, + "us-west-1": {Latitude: 38.8375215, Longitude: -120.8958242, City: "N. California", CountryCode: "US"}, + "us-west-2": {Latitude: 43.8041334, Longitude: -120.5542012, City: "Oregon", CountryCode: "US"}, + "ap-south-1": {Latitude: 19.0759837, Longitude: 72.8776559, City: "Mumbai", CountryCode: "IN"}, + "ap-northeast-3": {Latitude: 34.6937378, Longitude: 135.5021651, City: "Osaka-Local", CountryCode: "JP"}, + "ap-northeast-2": {Latitude: 37.566535, Longitude: 126.9779692, City: "Seoul", CountryCode: "KR"}, + "ap-southeast-1": {Latitude: 1.352083, Longitude: 103.819836, City: "Singapore", CountryCode: "SG"}, + "ap-southeast-2": {Latitude: -33.8688197, Longitude: 151.2092955, City: "Sydney", CountryCode: "AU"}, + "ap-northeast-1": {Latitude: 35.7090259, Longitude: 139.7319925, City: "Tokyo", CountryCode: "JP"}, + "ca-central-1": {Latitude: 56.130366, Longitude: -106.346771, City: "Central", CountryCode: "CA"}, + "cn-north-1": {Latitude: 39.90419989999999, Longitude: 116.4073963, City: "Beijing", CountryCode: "CN"}, + "cn-northwest-1": {Latitude: 37.198731, Longitude: 106.1580937, City: "Ningxia", CountryCode: "CN"}, + "eu-central-1": {Latitude: 50.1109221, Longitude: 8.6821267, City: "Frankfurt", CountryCode: "DE"}, + "eu-west-1": {Latitude: 53.41291, Longitude: -8.24389, City: "Ireland", CountryCode: "IE"}, + "eu-west-2": {Latitude: 51.5073509, Longitude: -0.1277583, City: "London", CountryCode: "GB"}, + "eu-west-3": {Latitude: 48.856614, Longitude: 2.3522219, City: "Paris", CountryCode: "FR"}, + "eu-north-1": {Latitude: 59.1946, Longitude: 18.47, City: "Stockholm", CountryCode: "SE"}, + "sa-east-1": {Latitude: -23.5505199, Longitude: -46.63330939999999, City: "San Paulo", CountryCode: "BR"}, + "us-gov-west-1": {Latitude: 37.09024, Longitude: -95.712891, City: "us-gov-west", CountryCode: "US"}, } diff --git a/pkg/util/aws/network.go b/pkg/util/aws/network.go index cdd46ea920..7c73f87aab 100644 --- a/pkg/util/aws/network.go +++ b/pkg/util/aws/network.go @@ -100,7 +100,7 @@ func (self *SNetwork) GetGateway() string { } func (self *SNetwork) GetServerType() string { - return models.SERVER_TYPE_GUEST + return models.NETWORK_TYPE_GUEST } func (self *SNetwork) GetIsPublic() bool { diff --git a/pkg/util/aws/provider/provider.go b/pkg/util/aws/provider/provider.go index 6dbb9a48e4..ceb9eeb620 100644 --- a/pkg/util/aws/provider/provider.go +++ b/pkg/util/aws/provider/provider.go @@ -1,8 +1,12 @@ package provider import ( + "context" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/aws" ) @@ -17,6 +21,41 @@ func (self *SAwsProviderFactory) ValidateChangeBandwidth(instanceId string, band return nil } +func (self *SAwsProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) error { + accessKeyID, _ := data.GetString("access_key_id") + if len(accessKeyID) == 0 { + return httperrors.NewMissingParameterError("access_key_id") + } + accessKeySecret, _ := data.GetString("access_key_secret") + if len(accessKeySecret) == 0 { + return httperrors.NewMissingParameterError("access_key_secret") + } + environment, _ := data.GetString("environment") + if len(environment) == 0 { + return httperrors.NewMissingParameterError("environment") + } + data.Set("account", jsonutils.NewString(accessKeyID)) + data.Set("secret", jsonutils.NewString(accessKeySecret)) + data.Set("access_url", jsonutils.NewString(environment)) + return nil +} + +func (self *SAwsProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, cloudaccount string) (*cloudprovider.SCloudaccount, error) { + accessKeyID, _ := data.GetString("access_key_id") + if len(accessKeyID) == 0 { + return nil, httperrors.NewMissingParameterError("access_key_id") + } + accessKeySecret, _ := data.GetString("access_key_secret") + if len(accessKeySecret) == 0 { + return nil, httperrors.NewMissingParameterError("access_key_secret") + } + account := &cloudprovider.SCloudaccount{ + Account: accessKeyID, + Secret: accessKeySecret, + } + return account, nil +} + func (self *SAwsProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) { client, err := aws.NewAwsClient(providerId, providerName, url, account, secret) if err != nil { @@ -85,3 +124,7 @@ func (self *SAwsProvider) GetBalance() (float64, error) { func (self *SAwsProvider) GetOnPremiseIRegion() (cloudprovider.ICloudRegion, error) { return nil, cloudprovider.ErrNotImplemented } + +func (self *SAwsProvider) SupportPrepaidResources() bool { + return true +} diff --git a/pkg/util/aws/region.go b/pkg/util/aws/region.go index 626192516d..b84071b4ca 100644 --- a/pkg/util/aws/region.go +++ b/pkg/util/aws/region.go @@ -441,7 +441,20 @@ func (self *SRegion) CreateInstanceSimple(name string, imgId string, cpu int, me log.Debugf("Search in zone %s", z.LocalName) net := z.getNetworkById(networkId) if net != nil { - inst, err := z.getHost().CreateVM(name, imgId, 0, cpu, memGB*1024, networkId, "", "", "", storageType, dataDiskSizesGB, publicKey, "", "", nil) + desc := &cloudprovider.SManagedVMCreateConfig{ + Name: name, + ExternalImageId: imgId, + SysDisk: cloudprovider.SDiskInfo{SizeGB: 0, StorageType: storageType}, + Cpu: cpu, + MemoryMB: memGB * 1024, + ExternalNetworkId: networkId, + DataDisks: []cloudprovider.SDiskInfo{}, + PublicKey: publicKey, + } + for _, sizeGB := range dataDiskSizesGB { + desc.DataDisks = append(desc.DataDisks, cloudprovider.SDiskInfo{SizeGB: sizeGB, StorageType: storageType}) + } + inst, err := z.getHost().CreateVM(desc) if err != nil { return nil, err } @@ -455,10 +468,34 @@ func (region *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, return nil, cloudprovider.ErrNotImplemented } -func (region *SRegion) GetILoadbalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { +func (region *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { return nil, cloudprovider.ErrNotImplemented } -func (region *SRegion) GetILoadbalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { +func (region *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { return nil, cloudprovider.ErrNotImplemented } diff --git a/pkg/util/aws/shell/image.go b/pkg/util/aws/shell/image.go index 8babcc0f2c..5f60846f32 100644 --- a/pkg/util/aws/shell/image.go +++ b/pkg/util/aws/shell/image.go @@ -7,19 +7,28 @@ import ( func init() { type ImageListOptions struct { - Status string `help:"image status type" choices:"Creating|Available|UnAvailable|CreateFailed"` - Owner string `help:"Owner type" choices:"amazon|self|microsoft|aws-marketplace"` - Id []string `help:"Image ID"` - Name string `help:"image name"` - Limit int `help:"page size"` - Offset int `help:"page offset"` + Status string `help:"image status type" choices:"Creating|Available|UnAvailable|CreateFailed"` + Owner string `help:"Owner type, e.g. self, system or all" choices:"self|system|all"` + VirtualizationType string `help:"virtualization type" choices:"hvm|paravirtual"` + Id []string `help:"Image ID"` + Name string `help:"image name"` + RawOwner []string `help:"raw owner id"` + VolumeType string `help:"image volume type" choices:"gp2|io1|st1|sc1|standard"` + Latest bool `help:"show latest image only"` } shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *aws.SRegion, args *ImageListOptions) error { - images, total, e := cli.GetImages(aws.ImageStatusType(args.Status), aws.ImageOwnerType(args.Owner), args.Id, args.Name, args.Offset, args.Limit) + var owners []aws.TImageOwnerType + switch args.Owner { + case "self": + owners = aws.ImageOwnerSelf + case "system": + owners = aws.ImageOwnerSystem + } + images, e := cli.GetImages(aws.ImageStatusType(args.Status), owners, args.Id, args.Name, args.VirtualizationType, args.RawOwner, args.VolumeType, args.Latest) if e != nil { return e } - printList(images, total, args.Offset, args.Limit, []string{}) + printList(images, 0, 0, 0, []string{}) return nil }) diff --git a/pkg/util/aws/storage.go b/pkg/util/aws/storage.go index 4da587ec70..6838248515 100644 --- a/pkg/util/aws/storage.go +++ b/pkg/util/aws/storage.go @@ -128,3 +128,7 @@ func (self *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, erro func (self *SStorage) GetMountPoint() string { return "" } + +func (self *SStorage) IsSysDiskStore() bool { + return true +} diff --git a/pkg/util/aws/storagecache.go b/pkg/util/aws/storagecache.go index 6a5b61ee94..31c185e23e 100644 --- a/pkg/util/aws/storagecache.go +++ b/pkg/util/aws/storagecache.go @@ -71,15 +71,12 @@ func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImag return nil, fmt.Errorf("GetIImageById image id should not be empty") } - parts, _, err := self.region.GetImages(ImageStatusType(""), ImageOwnerSelf, []string{extId}, "", 0, 1) + part, err := self.region.GetImage(extId) if err != nil { return nil, err } - if len(parts) == 0 { - return nil, cloudprovider.ErrNotFound - } - parts[0].storageCache = self - return &parts[0], nil + part.storageCache = self + return part, nil } func (self *SStoragecache) GetPath() string { @@ -91,20 +88,22 @@ func (self *SStoragecache) GetManagerId() string { } func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { - if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil { + imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc) + if err != nil { return nil, err - } else if image, err := self.region.GetImage(imageId); err != nil { - return nil, err - } else { - image.storageCache = self - iimage := make([]cloudprovider.ICloudImage, 1) - iimage[0] = image - //todo : implement me - if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil { - return nil, err - } - return iimage[0], nil } + image, err := self.region.GetImage(imageId) + if err != nil { + return nil, err + } + image.storageCache = self + iimage := make([]cloudprovider.ICloudImage, 1) + iimage[0] = image + //todo : implement me + if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil { + return nil, err + } + return iimage[0], nil } func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { @@ -131,16 +130,9 @@ func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.To } func (self *SStoragecache) fetchImages() error { - images := make([]SImage, 0) - for { - parts, total, err := self.region.GetImages(ImageStatusType(""), ImageOwnerSelf, nil, "", len(images), 50) - if err != nil { - return err - } - images = append(images, parts...) - if len(images) >= total { - break - } + images, err := self.region.GetImages("", ImageOwnerSelfSystem, nil, "", "hvm", nil, "", true) + if err != nil { + return err } self.iimages = make([]cloudprovider.ICloudImage, len(images)) for i := 0; i < len(images); i += 1 { @@ -220,7 +212,7 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To // check image name, avoid name conflict for { - _, err = self.region.GetImageByName(imageName) + _, err = self.region.GetImageByName(imageName, ImageOwnerSelf) if err != nil { if err == cloudprovider.ErrNotFound { break @@ -257,6 +249,8 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To log.Debugf("DescribeImportImage Task %s", ret.String()) for _, item := range ret.ImportImageTasks { if *item.Status == "completed" { + // add name tag + self.region.addTags(*item.ImageId, "Name", imageId) return *item.ImageId, nil } } @@ -534,3 +528,8 @@ func (self *SRegion) getStoragecache() *SStoragecache { } return self.storageCache } + +func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + storageCache := region.getStoragecache() + return []cloudprovider.ICloudStoragecache{storageCache}, nil +} diff --git a/pkg/util/aws/utils.go b/pkg/util/aws/utils.go index bf52f0174a..45e842107f 100644 --- a/pkg/util/aws/utils.go +++ b/pkg/util/aws/utils.go @@ -5,6 +5,7 @@ import ( "net" "reflect" "regexp" + "sort" "strings" "github.com/aws/aws-sdk-go/service/ec2" @@ -157,6 +158,34 @@ func IntVal(s *int64) int64 { return 0 } +// SecurityRuleSet to allow list +// 将安全组规则全部转换为等价的allow规则 +func SecurityRuleSetToAllowSet(srs secrules.SecurityRuleSet) secrules.SecurityRuleSet { + inRuleSet := secrules.SecurityRuleSet{} + outRuleSet := secrules.SecurityRuleSet{} + + for _, rule := range srs { + if rule.Direction == secrules.SecurityRuleIngress { + inRuleSet = append(inRuleSet, rule) + } + + if rule.Direction == secrules.SecurityRuleEgress { + outRuleSet = append(outRuleSet, rule) + } + } + + sort.Sort(inRuleSet) + sort.Sort(outRuleSet) + + inRuleSet = inRuleSet.AllowList() + outRuleSet = outRuleSet.AllowList() + + ret := secrules.SecurityRuleSet{} + ret = append(ret, inRuleSet...) + ret = append(ret, outRuleSet...) + return ret +} + func isAwsPermissionAllPorts(p ec2.IpPermission) bool { if p.FromPort == nil || p.ToPort == nil { return false diff --git a/pkg/util/aws/vpc.go b/pkg/util/aws/vpc.go index e4d18ca8ab..c12dbc2500 100644 --- a/pkg/util/aws/vpc.go +++ b/pkg/util/aws/vpc.go @@ -146,6 +146,7 @@ func (self *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name str secgroupId = fmt.Sprintf("%s-%s", vpcId, secgroupId) } + rules = SecurityRuleSetToAllowSet(rules) if secgroup, err := self.getSecurityGroupById(vpcId, secgroupId); err != nil { if len(desc) == 0 { desc = fmt.Sprintf("security group %s for vpc %s", name, vpcId) diff --git a/pkg/util/azure/azure.go b/pkg/util/azure/azure.go index 50fcc0e55c..89353eefe5 100644 --- a/pkg/util/azure/azure.go +++ b/pkg/util/azure/azure.go @@ -68,6 +68,7 @@ var DEFAULT_API_VERSION = map[string]string{ "Microsoft.ClassicNetwork/reservedIps": "2016-04-01", //2014-01-01,2014-06-01,2015-06-01,2015-12-01,2016-04-01,2016-11-01 "Microsoft.ClassicNetwork/networkSecurityGroups": "2016-11-01", //2015-06-01,2015-12-01,2016-04-01,2016-11-01 "Microsoft.ClassicCompute/domainNames": "2015-12-01", //2014-01-01, 2014-06-01, 2015-06-01, 2015-10-01, 2015-12-01, 2016-04-01, 2016-11-01, 2017-11-01, 2017-11-15 + "Microsoft.Compute/locations": "2018-06-01", } func NewAzureClient(providerId string, providerName string, accessKey string, secret string, envName string) (*SAzureClient, error) { @@ -140,6 +141,7 @@ func (self *SAzureClient) Get(resourceId string, params []string, retVal interfa if err != nil { return err } + fmt.Println(body) err = body.Unmarshal(retVal) if err != nil { return err @@ -172,6 +174,10 @@ func (self *SAzureClient) ListClassicDisks() (jsonutils.JSONObject, error) { } func (self *SAzureClient) ListAll(resourceType string, retVal interface{}) error { + return self.ListResources(resourceType, retVal, []string{"value"}) +} + +func (self *SAzureClient) ListResources(resourceType string, retVal interface{}, keys []string) error { cli, err := self.getDefaultClient() if err != nil { return err @@ -187,8 +193,9 @@ func (self *SAzureClient) ListAll(resourceType string, retVal interface{}) error if err != nil { return err } + // fmt.Printf("%s: %s\n", resourceType, body) if retVal != nil { - body.Unmarshal(retVal, "value") + return body.Unmarshal(retVal, keys...) } return nil } diff --git a/pkg/util/azure/classic_host.go b/pkg/util/azure/classic_host.go index 85988cc207..66e714b413 100644 --- a/pkg/util/azure/classic_host.go +++ b/pkg/util/azure/classic_host.go @@ -8,7 +8,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/util/billing" ) type SClassicHost struct { @@ -43,15 +42,7 @@ func (self *SClassicHost) Refresh() error { return nil } -func (self *SClassicHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, - networkId string, ipAddr string, desc string, passwd string, storageType string, - diskSizes []int, publicKey string, secgroupId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (self *SClassicHost) CreateVM2(name string, imgId string, sysDiskSize int, skuId string, - networkId string, ipAddr string, desc string, passwd string, storageType string, - diskSizes []int, publicKey string, secgroupId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { +func (self *SClassicHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { return nil, cloudprovider.ErrNotImplemented } diff --git a/pkg/util/azure/classic_network.go b/pkg/util/azure/classic_network.go index c266bf172b..df8a217fdf 100644 --- a/pkg/util/azure/classic_network.go +++ b/pkg/util/azure/classic_network.go @@ -92,7 +92,7 @@ func (self *SClassicNetwork) GetIsPublic() bool { } func (self *SClassicNetwork) GetServerType() string { - return models.SERVER_TYPE_GUEST + return models.NETWORK_TYPE_GUEST } func (self *SClassicNetwork) Refresh() error { diff --git a/pkg/util/azure/classic_storage.go b/pkg/util/azure/classic_storage.go index d1cf9acbcc..35126ed1c0 100644 --- a/pkg/util/azure/classic_storage.go +++ b/pkg/util/azure/classic_storage.go @@ -130,3 +130,7 @@ func (self *SClassicStorage) Refresh() error { func (self *SClassicStorage) GetMountPoint() string { return "" } + +func (self *SClassicStorage) IsSysDiskStore() bool { + return true +} diff --git a/pkg/util/azure/disk.go b/pkg/util/azure/disk.go index 9eec81b073..e9dfc53f6d 100644 --- a/pkg/util/azure/disk.go +++ b/pkg/util/azure/disk.go @@ -26,9 +26,10 @@ type ImageDiskReference struct { type CreationData struct { CreateOption string `json:"createOption,omitempty"` StorageAccountID string - ImageReference *ImageDiskReference `json:"imageReference,omitempty"` - SourceURI string `json:"sourceUri,omitempty"` - SourceResourceID string `json:"sourceResourceId,omitempty"` + // ImageReference *ImageDiskReference `json:"imageReference,omitempty"` + ImageReference *ImageReference `json:"imageReference,omitempty"` + SourceURI string `json:"sourceUri,omitempty"` + SourceResourceID string `json:"sourceResourceId,omitempty"` } type DiskProperties struct { @@ -71,17 +72,25 @@ func (self *SRegion) CreateDisk(storageType string, name string, sizeGb int32, d Type: "Microsoft.Compute/disks", } if len(imageId) > 0 { - image, err := self.GetImage(imageId) + image, err := self.GetImageById(imageId) if err != nil { return nil, err } - blobUrl := image.GetBlobUri() - if len(blobUrl) == 0 { - return nil, fmt.Errorf("failed to find blobUri for image %s", image.Name) - } - disk.Properties.CreationData = CreationData{ - CreateOption: "Import", - SourceURI: blobUrl, + if isPrivateImageID(image.ID) { + blobUrl := image.GetBlobUri() + if len(blobUrl) == 0 { + return nil, fmt.Errorf("failed to find blobUri for image %s", image.Name) + } + disk.Properties.CreationData = CreationData{ + CreateOption: "Import", + SourceURI: blobUrl, + } + } else { + imgRef := image.getImageReference() + disk.Properties.CreationData = CreationData{ + CreateOption: "FromImage", + ImageReference: &imgRef, + } } disk.Properties.OsType = image.GetOsType() } diff --git a/pkg/util/azure/eip.go b/pkg/util/azure/eip.go index 88fc697bc2..4b5db78ad4 100644 --- a/pkg/util/azure/eip.go +++ b/pkg/util/azure/eip.go @@ -68,7 +68,7 @@ func (region *SRegion) AllocateEIP(eipName string) (*SEipAddress, error) { return &eip, cloudprovider.WaitStatus(&eip, models.EIP_STATUS_READY, 10*time.Second, 300*time.Second) } -func (region *SRegion) CreateEIP(eipName string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) { +func (region *SRegion) CreateEIP(eipName string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) { return region.AllocateEIP(eipName) } diff --git a/pkg/util/azure/host.go b/pkg/util/azure/host.go index 2ffd102ccc..f012a921d0 100644 --- a/pkg/util/azure/host.go +++ b/pkg/util/azure/host.go @@ -10,7 +10,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/ansible" - "yunion.io/x/onecloud/pkg/util/billing" ) type SHost struct { @@ -67,18 +66,15 @@ func (self *SHost) searchNetorkInterface(IPAddr string, networkId string, secgro return nil, cloudprovider.ErrNotFound } -func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, - networkId string, ipAddr string, desc string, passwd string, storageType string, - diskSizes []int, publicKey string, secgroupId string, userData string, - bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - net := self.zone.getNetworkById(networkId) +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + net := self.zone.getNetworkById(desc.ExternalNetworkId) if net == nil { - return nil, fmt.Errorf("invalid network ID %s", networkId) + return nil, fmt.Errorf("invalid network ID %s", desc.ExternalNetworkId) } - nic, err := self.searchNetorkInterface(ipAddr, net.GetId(), secgroupId) + nic, err := self.searchNetorkInterface(desc.IpAddr, net.GetId(), desc.ExternalSecgroupId) if err != nil { if err == cloudprovider.ErrNotFound { - nic, err = self.zone.region.CreateNetworkInterface(fmt.Sprintf("%s-ipconfig", name), ipAddr, net.GetId(), secgroupId) + nic, err = self.zone.region.CreateNetworkInterface(fmt.Sprintf("%s-ipconfig", desc.Name), desc.IpAddr, net.GetId(), desc.ExternalSecgroupId) if err != nil { return nil, err } @@ -86,7 +82,7 @@ func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, return nil, err } } - vmId, err := self._createVM(name, imgId, int32(sysDiskSize), cpu, memMB, "", nic.ID, ipAddr, desc, passwd, storageType, diskSizes, publicKey, userData) + vmId, err := self._createVM(desc.Name, desc.ExternalImageId, desc.SysDisk, desc.Cpu, desc.MemoryMB, desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, desc.DataDisks, desc.PublicKey, desc.UserData) if err != nil { self.zone.region.DeleteNetworkInterface(nic.ID) return nil, err @@ -99,39 +95,8 @@ func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, } } -func (self *SHost) CreateVM2(name string, imgId string, sysDiskSize int, instanceType string, - networkId string, ipAddr string, desc string, passwd string, storageType string, - diskSizes []int, publicKey string, secgroupId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - net := self.zone.getNetworkById(networkId) - if net == nil { - return nil, fmt.Errorf("invalid network ID %s", networkId) - } - nic, err := self.searchNetorkInterface(ipAddr, net.GetId(), secgroupId) - if err != nil { - if err == cloudprovider.ErrNotFound { - nic, err = self.zone.region.CreateNetworkInterface(fmt.Sprintf("%s-ipconfig", name), ipAddr, net.GetId(), secgroupId) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - vmId, err := self._createVM(name, imgId, int32(sysDiskSize), 0, 0, instanceType, nic.ID, ipAddr, desc, passwd, storageType, diskSizes, publicKey, userData) - if err != nil { - self.zone.region.DeleteNetworkInterface(nic.ID) - return nil, err - } - if vm, err := self.zone.region.GetInstance(vmId); err != nil { - return nil, err - } else { - vm.host = self - return vm, err - } -} - -func (self *SHost) _createVM(name string, imgId string, sysDiskSize int32, cpu int, memMB int, instanceType string, nicId string, ipAddr string, desc string, passwd string, storageType string, diskSizes []int, publicKey string, userData string) (string, error) { - image, err := self.zone.region.GetImage(imgId) +func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SDiskInfo, cpu int, memMB int, instanceType string, nicId string, ipAddr string, desc string, passwd string, dataDisks []cloudprovider.SDiskInfo, publicKey string, userData string) (string, error) { + image, err := self.zone.region.GetImageById(imgId) if err != nil { log.Errorf("Get Image %s fail %s", imgId, err) return "", err @@ -141,11 +106,11 @@ func (self *SHost) _createVM(name string, imgId string, sysDiskSize int32, cpu i log.Errorf("image %s status %s", imgId, image.Properties.ProvisioningState) return "", fmt.Errorf("image not ready") } - storage, err := self.zone.getStorageByType(storageType) + storage, err := self.zone.getStorageByType(sysDisk.StorageType) if err != nil { - return "", fmt.Errorf("Storage %s not avaiable: %s", storageType, err) + return "", fmt.Errorf("Storage %s not avaiable: %s", sysDisk.StorageType, err) } - + sysDiskSize := int32(sysDisk.SizeGB) instance := SInstance{ Name: name, Location: self.zone.region.Name, @@ -167,9 +132,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDiskSize int32, cpu i }, }, StorageProfile: StorageProfile{ - ImageReference: ImageReference{ - ID: image.ID, - }, + ImageReference: image.getImageReference(), OsDisk: OSDisk{ Name: fmt.Sprintf("vdisk_%s_%d", name, time.Now().UnixNano()), Caching: "ReadWrite", @@ -195,20 +158,20 @@ func (self *SHost) _createVM(name string, imgId string, sysDiskSize int32, cpu i } } - dataDisks := []DataDisk{} - for i := 0; i < len(diskSizes); i++ { + _dataDisks := []DataDisk{} + for i := 0; i < len(dataDisks); i++ { diskName := fmt.Sprintf("vdisk_%s_%d", name, time.Now().UnixNano()) - size := int32(diskSizes[i]) + size := int32(dataDisks[i].SizeGB) lun := int32(i) - dataDisks = append(dataDisks, DataDisk{ + _dataDisks = append(_dataDisks, DataDisk{ Name: diskName, DiskSizeGB: &size, CreateOption: "Empty", Lun: lun, }) } - if len(dataDisks) > 0 { - instance.Properties.StorageProfile.DataDisks = dataDisks + if len(_dataDisks) > 0 { + instance.Properties.StorageProfile.DataDisks = _dataDisks } if len(instanceType) > 0 { diff --git a/pkg/util/azure/image.go b/pkg/util/azure/image.go index beb21825a1..c29f57c94d 100644 --- a/pkg/util/azure/image.go +++ b/pkg/util/azure/image.go @@ -2,10 +2,13 @@ package azure import ( "context" + "fmt" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -29,8 +32,9 @@ type ImageOSDisk struct { ManagedDisk *SubResource BlobURI string `json:"blobUri,omitempty"` Caching string `json:"caching,omitempty"` - DiskSizeGB int32 `json:"diskSizeGB,omitempty"` + DiskSizeGB int32 `json:"diskSizeGB,omitzero"` StorageAccountType string `json:"storageAccountType,omitempty"` + OperatingSystem string `json:"operatingSystem,omitempty"` } type ImageDataDisk struct { @@ -39,16 +43,18 @@ type ImageDataDisk struct { ManagedDisk SubResource BlobURI string Caching string - DiskSizeGB int32 + DiskSizeGB int32 `json:"diskSizeGB,omitzero"` StorageAccountType string } -type DataDisks []ImageDataDisk - type ImageStorageProfile struct { - OsDisk ImageOSDisk `json:"osDisk,omitempty"` - DataDisks *DataDisks - ZoneResilient *bool + OsDisk ImageOSDisk `json:"osDisk,omitempty"` + DataDisks []ImageDataDisk `json:"dataDisks,omitempty"` + ZoneResilient bool `json:"zoneResilient,omitfalse"` +} + +type SAutomaticOSUpgradeProperties struct { + AutomaticOSUpgradeSupported bool } type ImageProperties struct { @@ -61,10 +67,17 @@ type SImage struct { storageCache *SStoragecache Properties ImageProperties `json:"properties,omitempty"` - ID string + ID string `json:"id,omitempty"` Name string - Type string `` + Type string Location string + + Publisher string + Offer string + Sku string + Version string + + ImageType string } func (self *SImage) GetMetadata() *jsonutils.JSONDict { @@ -95,41 +108,122 @@ func (self *SImage) GetGlobalId() string { func (self *SImage) GetStatus() string { switch self.Properties.ProvisioningState { case "created": - return models.IMAGE_STATUS_QUEUED + return models.CACHED_IMAGE_STATUS_CACHING case "Succeeded": - return models.IMAGE_STATUS_ACTIVE + return models.CACHED_IMAGE_STATUS_READY default: log.Errorf("Unknow image status: %s", self.Properties.ProvisioningState) - return models.IMAGE_STATUS_KILLED + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (self *SImage) GetImageStatus() string { + switch self.Properties.ProvisioningState { + case "created": + return cloudprovider.IMAGE_STATUS_QUEUED + case "Succeeded": + return cloudprovider.IMAGE_STATUS_ACTIVE + default: + log.Errorf("Unknow image status: %s", self.Properties.ProvisioningState) + return cloudprovider.IMAGE_STATUS_KILLED } } func (self *SImage) Refresh() error { - new, err := self.storageCache.region.GetImage(self.Name) + new, err := self.storageCache.region.GetImageById(self.ID) if err != nil { return err } return jsonutils.Update(self, new) } +func (self *SImage) GetImageType() string { + return self.ImageType +} + +func (self *SImage) GetSize() int64 { + return int64(self.Properties.StorageProfile.OsDisk.DiskSizeGB) * 1024 * 1024 * 1024 +} + +func (self *SImage) isPublic() bool { + if self.ImageType == cloudprovider.CachedImageTypeCustomized { + return false + } + return true +} + +func (self *SImage) GetOsType() string { + osType := self.Properties.StorageProfile.OsDisk.OsType + if len(osType) == 0 { + osType = publisherGetOsType(self.Publisher) + } + return osType +} + +func (self *SImage) GetOsArch() string { + if self.ImageType == cloudprovider.CachedImageTypeCustomized { + return "x86_64" + } + return publisherGetOsArch(self.Publisher, self.Offer, self.Sku, self.Version) +} + +func (self *SImage) GetOsDist() string { + if self.ImageType == cloudprovider.CachedImageTypeCustomized { + return "" + } + return publisherGetOsDist(self.Publisher, self.Offer, self.Sku, self.Version) +} + +func (self *SImage) GetOsVersion() string { + return publisherGetOsVersion(self.Publisher, self.Offer, self.Sku, self.Version) +} + +func (self *SImage) GetMinOsDiskSizeGb() int { + return 10 +} + +func (self *SImage) GetImageFormat() string { + return "vhd" +} + +func (self *SImage) GetCreateTime() time.Time { + return time.Time{} +} + func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { return self.storageCache } func (self *SRegion) GetImageStatus(imageId string) (ImageStatusType, error) { - if image, err := self.GetImage(imageId); err != nil { + if image, err := self.GetImageById(imageId); err != nil { return "", err } else { return image.Properties.ProvisioningState, nil } } -func (self *SRegion) GetImage(imageId string) (*SImage, error) { - image := SImage{} - return &image, self.client.Get(imageId, []string{}, &image) +func isPrivateImageID(imageId string) bool { + return strings.HasPrefix(strings.ToLower(imageId), "/subscriptions/") } -func (self *SRegion) GetImageByName(name string) (*SImage, error) { +func (self *SRegion) GetImageById(imageId string) (SImage, error) { + if isPrivateImageID(imageId) { + return self.getPrivateImage(imageId) + } else { + return self.getOfferedImage(imageId) + } +} + +func (self *SRegion) getPrivateImage(imageId string) (SImage, error) { + image := SImage{} + err := self.client.Get(imageId, []string{}, &image) + if err != nil { + return image, err + } + return image, nil +} + +/* func (self *SRegion) GetImageByName(name string) (*SImage, error) { images := []SImage{} err := self.client.ListAll("Microsoft.Compute/images", &images) if err != nil { @@ -155,7 +249,7 @@ func (self *SRegion) GetImageById(idstr string) (*SImage, error) { } } return nil, cloudprovider.ErrNotFound -} +}*/ func (self *SRegion) CreateImageByBlob(imageName, osType, blobURI string, diskSizeGB int32) (*SImage, error) { if diskSizeGB < 1 || diskSizeGB > 4095 { @@ -199,7 +293,58 @@ func (self *SRegion) CreateImage(snapshotId, imageName, osType, imageDesc string return &image, self.client.Create(jsonutils.Marshal(image), &image) } -func (self *SRegion) GetImages() ([]SImage, error) { +func (self *SRegion) getOfferedImages(publishersFilter []string, offersFilter []string, skusFilter []string, verFilter []string, imageType string, latestVer bool) ([]SImage, error) { + images := make([]SImage, 0) + idList, err := self.GetOfferedImageIDs(publishersFilter, offersFilter, skusFilter, verFilter, latestVer) + if err != nil { + return nil, err + } + for _, id := range idList { + image, err := self.getOfferedImage(id) + image.ImageType = imageType + if err == nil { + images = append(images, image) + } + } + return images, nil +} + +func (self *SRegion) GetOfferedImageIDs(publishersFilter []string, offersFilter []string, skusFilter []string, verFilter []string, latestVer bool) ([]string, error) { + idList := make([]string, 0) + publishers, err := self.GetImagePublishers(toLowerStringArray(publishersFilter)) + if err != nil { + return nil, err + } + for _, publisher := range publishers { + offers, err := self.getImageOffers(publisher, toLowerStringArray(offersFilter)) + if err != nil { + return nil, err + } + for _, offer := range offers { + skus, err := self.getImageSkus(publisher, offer, toLowerStringArray(skusFilter)) + if err != nil { + return nil, err + } + for _, sku := range skus { + verFilter = toLowerStringArray(verFilter) + vers, err := self.getImageVersions(publisher, offer, sku, verFilter) + if err != nil { + return nil, err + } + if latestVer && len(vers) > 0 { + vers = []string{vers[len(vers)-1]} + } + for _, ver := range vers { + idStr := strings.Join([]string{publisher, offer, sku, ver}, "/") + idList = append(idList, idStr) + } + } + } + } + return idList, nil +} + +func (self *SRegion) getPrivateImages() ([]SImage, error) { result := []SImage{} images := []SImage{} err := self.client.ListAll("Microsoft.Compute/images", &images) @@ -208,18 +353,49 @@ func (self *SRegion) GetImages() ([]SImage, error) { } for i := 0; i < len(images); i++ { if images[i].Location == self.Name { + images[i].ImageType = cloudprovider.CachedImageTypeCustomized result = append(result, images[i]) } } return result, nil } +func toLowerStringArray(input []string) []string { + output := make([]string, len(input)) + for i := range input { + output[i] = strings.ToLower(input[i]) + } + return output +} + +func (self *SRegion) GetImages(imageType string) ([]SImage, error) { + images := make([]SImage, 0) + if len(imageType) == 0 { + ret, _ := self.getPrivateImages() + if len(ret) > 0 { + images = append(images, ret...) + } + ret, _ = self.getOfferedImages(knownPublishers, nil, nil, nil, cloudprovider.CachedImageTypeSystem, true) + if len(ret) > 0 { + images = append(images, ret...) + } + return images, nil + } + switch imageType { + case cloudprovider.CachedImageTypeCustomized: + return self.getPrivateImages() + case cloudprovider.CachedImageTypeSystem: + return self.getOfferedImages(knownPublishers, nil, nil, nil, cloudprovider.CachedImageTypeSystem, true) + default: + return self.getOfferedImages(nil, nil, nil, nil, cloudprovider.CachedImageTypeMarket, true) + } +} + func (self *SRegion) DeleteImage(imageId string) error { return self.client.Delete(imageId) } func (self *SImage) GetBlobUri() string { - return self.Properties.StorageProfile.OsDisk.BlobURI } @@ -227,6 +403,110 @@ func (self *SImage) Delete(ctx context.Context) error { return self.storageCache.region.DeleteImage(self.ID) } -func (self *SImage) GetOsType() string { - return string(self.Properties.StorageProfile.OsDisk.OsType) +type SAzureImageResource struct { + Id string + Name string + Location string +} + +func (region *SRegion) GetImagePublishers(filter []string) ([]string, error) { + publishers := make([]SAzureImageResource, 0) + err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers", region.Name), &publishers, nil) + if err != nil { + return nil, err + } + ret := make([]string, 0) + for i := range publishers { + if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(publishers[i].Name), filter) { + ret = append(ret, publishers[i].Name) + } + } + return ret, nil +} + +func (region *SRegion) getImageOffers(publisher string, filter []string) ([]string, error) { + offsers := make([]SAzureImageResource, 0) + err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers", region.Name, publisher), &offsers, nil) + if err != nil { + return nil, err + } + ret := make([]string, 0) + for i := range offsers { + if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(offsers[i].Name), filter) { + ret = append(ret, offsers[i].Name) + } + } + return ret, nil +} + +func (region *SRegion) getImageSkus(publisher string, offser string, filter []string) ([]string, error) { + skus := make([]SAzureImageResource, 0) + err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus", region.Name, publisher, offser), &skus, nil) + if err != nil { + return nil, err + } + ret := make([]string, 0) + for i := range skus { + if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(skus[i].Name), filter) { + ret = append(ret, skus[i].Name) + } + } + return ret, nil +} + +func (region *SRegion) getImageVersions(publisher string, offer string, sku string, filter []string) ([]string, error) { + vers := make([]SAzureImageResource, 0) + err := region.client.ListResources(fmt.Sprintf("Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions", region.Name, publisher, offer, sku), &vers, nil) + if err != nil { + return nil, err + } + ret := make([]string, 0) + for i := range vers { + if len(filter) == 0 || utils.IsInStringArray(strings.ToLower(vers[i].Name), filter) { + ret = append(ret, vers[i].Name) + } + } + return ret, nil +} + +func (region *SRegion) getOfferedImage(offerId string) (SImage, error) { + image := SImage{} + + parts := strings.Split(offerId, "/") + if len(parts) < 4 { + return image, fmt.Errorf("invalid image ID %s", offerId) + } + publisher := parts[0] + offer := parts[1] + sku := parts[2] + version := parts[3] + //err := region.client.Get(fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s", region.client.subscriptionId, region.Name, publisher, offer, sku, version), nil, &image) + //if err != nil { + // return image, err + //} + image.ID = offerId + image.Location = region.Name + image.Type = "Microsoft.Compute/vmimage" + image.Name = publisherGetName(publisher, offer, sku, version) + image.Publisher = publisher + image.Offer = offer + image.Sku = sku + image.Version = version + image.Properties.ProvisioningState = ImageStatusAvailable + return image, nil +} + +func (image *SImage) getImageReference() ImageReference { + if isPrivateImageID(image.ID) { + return ImageReference{ + ID: image.ID, + } + } else { + return ImageReference{ + Sku: image.Sku, + Publisher: image.Publisher, + Version: image.Version, + Offer: image.Offer, + } + } } diff --git a/pkg/util/azure/instance.go b/pkg/util/azure/instance.go index bbb935fef6..38e07c1f50 100644 --- a/pkg/util/azure/instance.go +++ b/pkg/util/azure/instance.go @@ -696,7 +696,7 @@ func (region *SRegion) ReplaceSystemDisk(instanceId, imageId, passwd, publicKey if err != nil { return "", err } - image, err := region.GetImage(imageId) + image, err := region.GetImageById(imageId) if err != nil { return "", err } diff --git a/pkg/util/azure/network.go b/pkg/util/azure/network.go index 72eec45339..a3eea9ac0c 100644 --- a/pkg/util/azure/network.go +++ b/pkg/util/azure/network.go @@ -98,7 +98,7 @@ func (self *SNetwork) GetIsPublic() bool { } func (self *SNetwork) GetServerType() string { - return models.SERVER_TYPE_GUEST + return models.NETWORK_TYPE_GUEST } func (self *SNetwork) Refresh() error { diff --git a/pkg/util/azure/provider/provider.go b/pkg/util/azure/provider/provider.go index f4fa7bb6e5..277414dc9b 100644 --- a/pkg/util/azure/provider/provider.go +++ b/pkg/util/azure/provider/provider.go @@ -1,10 +1,13 @@ package provider import ( + "context" "fmt" "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/azure" // "yunion.io/x/log" ) @@ -20,6 +23,45 @@ func (self *SAzureProviderFactory) ValidateChangeBandwidth(instanceId string, ba return fmt.Errorf("Changing %s bandwidth is not supported", azure.CLOUD_PROVIDER_AZURE) } +func (self *SAzureProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) error { + directoryID, _ := data.GetString("directory_id") + if len(directoryID) == 0 { + return httperrors.NewMissingParameterError("directory_id") + } + clientID, _ := data.GetString("client_id") + if len(clientID) == 0 { + return httperrors.NewMissingParameterError("client_id") + } + clientSecret, _ := data.GetString("client_secret") + if len(clientSecret) == 0 { + return httperrors.NewMissingParameterError("client_secret") + } + environment, _ := data.GetString("environment") + if len(environment) == 0 { + return httperrors.NewMissingParameterError("environment") + } + data.Set("account", jsonutils.NewString(directoryID)) + data.Set("secret", jsonutils.NewString(fmt.Sprintf("%s/%s", clientID, clientSecret))) + data.Set("access_url", jsonutils.NewString(environment)) + return nil +} + +func (self *SAzureProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, cloudaccount string) (*cloudprovider.SCloudaccount, error) { + clientID, _ := data.GetString("client_id") + if len(clientID) == 0 { + return nil, httperrors.NewMissingParameterError("client_id") + } + clientSecret, _ := data.GetString("client_secret") + if len(clientSecret) == 0 { + return nil, httperrors.NewMissingParameterError("client_secret") + } + account := &cloudprovider.SCloudaccount{ + Account: cloudaccount, + Secret: fmt.Sprintf("%s/%s", clientID, clientSecret), + } + return account, nil +} + func (self *SAzureProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) { if client, err := azure.NewAzureClient(providerId, providerName, account, secret, url); err != nil { return nil, err @@ -88,3 +130,7 @@ func (self *SAzureProvider) GetBalance() (float64, error) { func (self *SAzureProvider) GetOnPremiseIRegion() (cloudprovider.ICloudRegion, error) { return nil, cloudprovider.ErrNotImplemented } + +func (self *SAzureProvider) SupportPrepaidResources() bool { + return true +} diff --git a/pkg/util/azure/publisher.go b/pkg/util/azure/publisher.go new file mode 100644 index 0000000000..e428dd4c15 --- /dev/null +++ b/pkg/util/azure/publisher.go @@ -0,0 +1,198 @@ +package azure + +import ( + "fmt" + "strings" +) + +type SPublisherDriver struct { + OsType string + GetOsDist func(offser, sku, version string) string + GetOsVersion func(offser, sku, version string) string + GetOsArch func(offser, sku, version string) string + GetName func(offser, sku, version string) string +} + +var publisherDrivers = map[string]SPublisherDriver{ + // Microsoft Windows Server + "MicrosoftWindowsServer": { + OsType: "Windows", + GetOsDist: func(offer, sku, version string) string { + parts := strings.Split(sku, "-") + return fmt.Sprintf("Windows Server %s", strings.Join(parts, " ")) + }, + GetOsVersion: func(offer, sku, version string) string { + parts := strings.Split(sku, "-") + return parts[0] + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s-%s", offer, sku, version) + }, + }, + // RHEL + "RedHat": { + OsType: "Linux", + GetOsDist: func(offer, sku, version string) string { + return "RHEL" + }, + GetOsVersion: func(offer, sku, version string) string { + return sku + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s", offer, version) + }, + }, + // Ubuntu + "Canonical": { + OsType: "Linux", + GetOsDist: func(offer, sku, version string) string { + return "Ubuntu" + }, + GetOsVersion: func(offer, sku, version string) string { + return sku + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s", offer, version) + }, + }, + // CentOS + "OpenLogic": { + OsType: "Linux", + GetOsDist: func(offer, sku, version string) string { + return "CentOS" + }, + GetOsVersion: func(offer, sku, version string) string { + return sku + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s", offer, version) + }, + }, + // SUSE + "SUSE": { + OsType: "Linux", + GetOsDist: func(offer, sku, version string) string { + return "SUSE" + }, + GetOsVersion: func(offer, sku, version string) string { + return sku + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s-%s", offer, sku, version) + }, + }, + // CoreOS + "CoreOS": { + OsType: "Linux", + GetOsDist: func(offer, sku, version string) string { + return "CoreOS" + }, + GetOsVersion: func(offer, sku, version string) string { + return version + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s-%s", offer, sku, version) + }, + }, + // Debian + "credativ": { + OsType: "Linux", + GetOsDist: func(offer, sku, version string) string { + return "Debian" + }, + GetOsVersion: func(offer, sku, version string) string { + return sku + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s", offer, version) + }, + }, + // FreeBSD + "MicrosoftOSTC": { + OsType: "FreeBSD", + GetOsDist: func(offer, sku, version string) string { + return "FreeBSD" + }, + GetOsVersion: func(offer, sku, version string) string { + return sku + }, + GetOsArch: func(offer, sku, version string) string { + return "x86_64" + }, + GetName: func(offer, sku, version string) string { + return fmt.Sprintf("%s-%s", offer, version) + }, + }, +} + +var knownPublishers []string + +func init() { + knownPublishers = make([]string, len(publisherDrivers)) + i := 0 + for k := range publisherDrivers { + knownPublishers[i] = strings.ToLower(k) + i += 1 + } +} + +func publisherGetName(publisher, offer, sku, version string) string { + driver, ok := publisherDrivers[publisher] + if ok { + return driver.GetName(offer, sku, version) + } + return fmt.Sprintf("%s-%s-%s", offer, sku, version) +} + +func publisherGetOsType(publisher string) string { + driver, ok := publisherDrivers[publisher] + if ok { + return driver.OsType + } + return "Linux" +} + +func publisherGetOsDist(publisher, offer, sku, version string) string { + driver, ok := publisherDrivers[publisher] + if ok { + return driver.GetOsDist(offer, sku, version) + } + return offer +} + +func publisherGetOsVersion(publisher, offer, sku, version string) string { + driver, ok := publisherDrivers[publisher] + if ok { + return driver.GetOsVersion(offer, sku, version) + } + return sku +} + +func publisherGetOsArch(publisher, offer, sku, version string) string { + driver, ok := publisherDrivers[publisher] + if ok { + return driver.GetOsArch(offer, sku, version) + } + return "x86_64" +} diff --git a/pkg/util/azure/region.go b/pkg/util/azure/region.go index 00ebbac4df..28df064f46 100644 --- a/pkg/util/azure/region.go +++ b/pkg/util/azure/region.go @@ -407,8 +407,21 @@ func (self *SRegion) CreateInstanceSimple(name string, imgId string, cpu int, me log.Debugf("Search in zone %s", z.Name) net := z.getNetworkById(networkId) if net != nil { - passwd := seclib2.RandomPassword2(12) - inst, err := z.getHost().CreateVM(name, imgId, 30, cpu, memGB*1024, networkId, "", "", passwd, storageType, dataDiskSizesGB, publicKey, "", "", nil) + desc := &cloudprovider.SManagedVMCreateConfig{ + Name: name, + ExternalImageId: imgId, + SysDisk: cloudprovider.SDiskInfo{SizeGB: 0, StorageType: storageType}, + Cpu: cpu, + MemoryMB: memGB * 1024, + ExternalNetworkId: networkId, + Password: seclib2.RandomPassword2(12), + DataDisks: []cloudprovider.SDiskInfo{}, + PublicKey: publicKey, + } + for _, sizeGB := range dataDiskSizesGB { + desc.DataDisks = append(desc.DataDisks, cloudprovider.SDiskInfo{SizeGB: sizeGB, StorageType: storageType}) + } + inst, err := z.getHost().CreateVM(desc) if err != nil { return nil, err } @@ -507,10 +520,34 @@ func (region *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, return nil, cloudprovider.ErrNotImplemented } -func (region *SRegion) GetILoadbalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { +func (region *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { return nil, cloudprovider.ErrNotImplemented } -func (region *SRegion) GetILoadbalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { +func (region *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { return nil, cloudprovider.ErrNotImplemented } diff --git a/pkg/util/azure/shell/image.go b/pkg/util/azure/shell/image.go index 9dbf4302a7..54abeb4ce0 100644 --- a/pkg/util/azure/shell/image.go +++ b/pkg/util/azure/shell/image.go @@ -1,24 +1,56 @@ package shell import ( + "fmt" + "sort" "yunion.io/x/onecloud/pkg/util/azure" "yunion.io/x/onecloud/pkg/util/shellutils" ) func init() { type ImageListOptions struct { - Limit int `help:"page size"` - Offset int `help:"page offset"` + ImageType string `help:"image type" choices:"customized|system|shared|market"` } shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *azure.SRegion, args *ImageListOptions) error { - if images, err := cli.GetImages(); err != nil { + if images, err := cli.GetImages(args.ImageType); err != nil { return err } else { - printList(images, len(images), args.Offset, args.Limit, []string{}) + printList(images, len(images), 0, 0, []string{}) return nil } }) + type ImagePublishersOptions struct { + } + shellutils.R(&ImagePublishersOptions{}, "image-publisher-list", "List image providers", func(cli *azure.SRegion, args *ImagePublishersOptions) error { + providers, err := cli.GetImagePublishers(nil) + if err != nil { + return err + } + sort.Strings(providers) + fmt.Println(providers) + return nil + }) + + type ImageOfferedIDOptions struct { + Publisher []string `help:"publisher candidates"` + Offer []string `help:"offer candidates"` + Sku []string `help:"sku candidates"` + Version []string `help:"version candidates"` + Latest bool `help:"show latest version only"` + } + shellutils.R(&ImageOfferedIDOptions{}, "public-image-id-list", "List image providers", func(cli *azure.SRegion, args *ImageOfferedIDOptions) error { + idList, err := cli.GetOfferedImageIDs(args.Publisher, args.Offer, args.Sku, args.Version, args.Latest) + if err != nil { + return err + } + sort.Strings(idList) + for _, id := range idList { + fmt.Println(id) + } + return nil + }) + type ImageCreateOptions struct { NAME string `helo:"Image name"` OSTYPE string `helo:"Operation system" choices:"Linux|Windows"` diff --git a/pkg/util/azure/shell/storageaccount.go b/pkg/util/azure/shell/storageaccount.go index 3d3bbf167b..c48895c479 100644 --- a/pkg/util/azure/shell/storageaccount.go +++ b/pkg/util/azure/shell/storageaccount.go @@ -30,12 +30,12 @@ func init() { }) shellutils.R(&StorageAccountOptions{}, "storage-account-show", "Show storage account detail", func(cli *azure.SRegion, args *StorageAccountOptions) error { - if account, err := cli.GetStorageAccountDetail(args.ID); err != nil { + account, err := cli.GetStorageAccountDetail(args.ID) + if err != nil { return err - } else { - printObject(account) - return nil } + printObject(account) + return nil }) shellutils.R(&StorageAccountOptions{}, "storage-account-key", "Get storage account key", func(cli *azure.SRegion, args *StorageAccountOptions) error { @@ -47,6 +47,75 @@ func init() { } }) + shellutils.R(&StorageAccountOptions{}, "storage-container-list", "Get list of containers of a storage account", func(cli *azure.SRegion, args *StorageAccountOptions) error { + account, err := cli.GetStorageAccountDetail(args.ID) + if err != nil { + return err + } + containers, err := account.GetContainers() + if err != nil { + return err + } + printList(containers, len(containers), 0, 0, nil) + return nil + }) + + type StorageAccountCreateContainerOptions struct { + ACCOUNT string `help:"storage account ID"` + CONTAINER string `help:"name of container to create"` + } + shellutils.R(&StorageAccountCreateContainerOptions{}, "storage-container-create", "Create a container in a storage account", func(cli *azure.SRegion, args *StorageAccountCreateContainerOptions) error { + account, err := cli.GetStorageAccountDetail(args.ACCOUNT) + if err != nil { + return err + } + container, err := account.CreateContainer(args.CONTAINER) + if err != nil { + return err + } + printObject(container) + return nil + }) + + shellutils.R(&StorageAccountCreateContainerOptions{}, "storage-container-list-objects", "Create a container in a storage account", func(cli *azure.SRegion, args *StorageAccountCreateContainerOptions) error { + account, err := cli.GetStorageAccountDetail(args.ACCOUNT) + if err != nil { + return err + } + container, err := account.GetContainer(args.CONTAINER) + if err != nil { + return err + } + blobs, err := container.ListFiles() + if err != nil { + return err + } + printList(blobs, len(blobs), 0, 0, nil) + return nil + }) + + type StorageAccountUploadOptions struct { + ACCOUNT string `help:"storage account ID"` + CONTAINER string `help:"name of container to create"` + FILE string `help:"local file to upload"` + } + shellutils.R(&StorageAccountUploadOptions{}, "storage-container-upload", "Upload a container in a storage account", func(cli *azure.SRegion, args *StorageAccountUploadOptions) error { + account, err := cli.GetStorageAccountDetail(args.ACCOUNT) + if err != nil { + return err + } + container, err := account.GetContainer(args.CONTAINER) + if err != nil { + return err + } + url, err := container.UploadFile(args.FILE) + if err != nil { + return err + } + fmt.Println(url) + return nil + }) + type StorageAccountCreateOptions struct { NAME string `help:"StorageAccount NAME"` } diff --git a/pkg/util/azure/storage.go b/pkg/util/azure/storage.go index e1c2caa023..3a1917554c 100644 --- a/pkg/util/azure/storage.go +++ b/pkg/util/azure/storage.go @@ -164,3 +164,7 @@ func (self *SStorage) Refresh() error { func (self *SStorage) GetMountPoint() string { return "" } + +func (self *SStorage) IsSysDiskStore() bool { + return true +} diff --git a/pkg/util/azure/storageaccount.go b/pkg/util/azure/storageaccount.go index 8b146f0fcb..26ff8a5ffb 100644 --- a/pkg/util/azure/storageaccount.go +++ b/pkg/util/azure/storageaccount.go @@ -156,7 +156,11 @@ func (self *SRegion) getStorageAccountID(storageAccount string) (*SStorageAccoun func (self *SRegion) GetStorageAccountDetail(accountId string) (*SStorageAccount, error) { account := SStorageAccount{region: self} - return &account, self.client.Get(accountId, []string{}, &account) + err := self.client.Get(accountId, []string{}, &account) + if err != nil { + return nil, err + } + return &account, nil } type AccountKeys struct { @@ -269,6 +273,19 @@ func (self *SStorageAccount) GetContainers() ([]SContainer, error) { return containers, nil } +func (self *SStorageAccount) GetContainer(name string) (*SContainer, error) { + containers, err := self.GetContainers() + if err != nil { + return nil, err + } + for i := range containers { + if containers[i].Name == name { + return &containers[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + func (self *SContainer) ListFiles() ([]storage.Blob, error) { storageaccount := self.storageaccount client, err := storage.NewBasicClientOnSovereignCloud(storageaccount.Name, storageaccount.accountKey, storageaccount.region.client.env) diff --git a/pkg/util/azure/storagecache.go b/pkg/util/azure/storagecache.go index f0f8fc4516..46e80447f9 100644 --- a/pkg/util/azure/storagecache.go +++ b/pkg/util/azure/storagecache.go @@ -66,7 +66,7 @@ func (self *SStoragecache) GetManagerId() string { } func (self *SStoragecache) fetchImages() error { - if images, err := self.region.GetImages(); err != nil { + if images, err := self.region.GetImages(""); err != nil { return err } else { self.iimages = make([]cloudprovider.ICloudImage, len(images)) @@ -93,7 +93,7 @@ func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImag return nil, err } img.storageCache = self - return img, nil + return &img, nil } func (self *SStoragecache) GetPath() string { @@ -227,7 +227,9 @@ func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imag } func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { - if image, err := self.region.GetImage(extId); err != nil { + // TODO: need to fix scenarios where image is a public image + // XXX Qiu Jian + if image, err := self.region.getPrivateImage(extId); err != nil { return nil, err } else if snapshotId := image.Properties.StorageProfile.OsDisk.Snapshot.ID; len(snapshotId) == 0 { return nil, cloudprovider.ErrNotFound @@ -292,3 +294,8 @@ func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imag } } + +func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + storageCache := region.getStoragecache() + return []cloudprovider.ICloudStoragecache{storageCache}, nil +} diff --git a/pkg/util/bwutils/bwutils.go b/pkg/util/bwutils/bwutils.go new file mode 100644 index 0000000000..007587f474 --- /dev/null +++ b/pkg/util/bwutils/bwutils.go @@ -0,0 +1,67 @@ +package bwutils + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/util/netutils" +) + +func GetBwValue(nicDesc jsonutils.JSONObject) int { + bw, err := nicDesc.Int("bw") + if err != nil { + ip, err := nicDesc.GetString("ip") + if err != nil { + bw = 1 + } else { + ipv4, err := netutils.NewIPV4Addr(ip) + if err == nil && netutils.IsExitAddress(ipv4) { + bw = 1 + } else { + bw = 10000 + } + } + } + return int(bw) +} + +func GetDownloadBwValue(nicDesc jsonutils.JSONObject, bwDownloadBandwidth int) (int, error) { + ip, _ := nicDesc.GetString("ip") + ifname, _ := nicDesc.GetString("ifname") + if len(ip) > 0 { + ipv4, err := netutils.NewIPV4Addr(ip) + if err != nil { + return 0, err + } + if netutils.IsExitAddress(ipv4) && len(ifname) > 0 && bwDownloadBandwidth > 0 { + bw := GetBwValue(nicDesc) + if bw > bwDownloadBandwidth { + return bw, nil + } else { + return bwDownloadBandwidth, nil + } + } + } + return 0, nil +} + +func GetOvsBwValues(nicDesc jsonutils.JSONObject) (int, int, error) { + var bwOvs int + bw := GetBwValue(nicDesc) + ip, err := nicDesc.GetString("ip") + if err == nil { + ipv4, err := netutils.NewIPV4Addr(ip) + if err != nil { + return 0, 0, err + } + if netutils.IsExitAddress(ipv4) { + bwOvs = 1000 + if bwOvs > bw*15 { + bwOvs = bw * 15 + } + } else { + bwOvs = bw + } + } else { + bwOvs = bw + } + return bwOvs * 1000, bwOvs * 2000, nil +} diff --git a/pkg/util/bwutils/doc.go b/pkg/util/bwutils/doc.go new file mode 100644 index 0000000000..c05dcc3a43 --- /dev/null +++ b/pkg/util/bwutils/doc.go @@ -0,0 +1 @@ +package bwutils // import "yunion.io/x/onecloud/pkg/util/bwutils" diff --git a/pkg/util/cgrouputils/cgrouputils.go b/pkg/util/cgrouputils/cgrouputils.go new file mode 100644 index 0000000000..1a71767992 --- /dev/null +++ b/pkg/util/cgrouputils/cgrouputils.go @@ -0,0 +1,587 @@ +package cgrouputils + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +const ( + CGROUP_PATH_UBUNTU = "/sys/fs/cgroup" + CGROUP_PATH_CENTOS = "/cgroup" + CGROUP_TASKS = "tasks" + + maxWeight = 16 + normalizeBase = 1024 +) + +var ( + cgroupsPath = getGroupPath() +) + +type ICGroupTask interface { + SetPid(string) + SetWeight(coreNum int) + SetHand(hand ICGroupTask) + + GetStaticConfig() map[string]string + GetConfig() map[string]string + Module() string + RemoveTask() bool + SetTask() bool + + init() bool +} + +type CGroupTask struct { + pid string + weight float64 + + hand ICGroupTask +} + +func NewCGroupTask(pid string, coreNum int) *CGroupTask { + return &CGroupTask{ + pid: pid, + weight: float64(coreNum) / normalizeBase, + } +} + +func getGroupPath() string { + if fileutils2.Exists(CGROUP_PATH_UBUNTU) { + return CGROUP_PATH_UBUNTU + } else { + return CGROUP_PATH_CENTOS + } +} + +func CgroupIsMounted() bool { + _, err := procutils.NewCommand("mountpoint", cgroupsPath).Run() + return err == nil +} + +func ModuleIsMounted(module string) bool { + fullPath := path.Join(cgroupsPath, module) + if fi, err := os.Lstat(fullPath); err != nil { + log.Errorln(err) + return false + } else if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + // is link + fullPath, err = filepath.EvalSymlinks(fullPath) + if err != nil { + log.Errorln(err) + } + } + _, err := procutils.NewCommand("mountpoint", fullPath).Run() + return err == nil +} + +func RootTaskPath(module string) string { + return path.Join(cgroupsPath, module) +} + +func GetTaskParamPath(module, name, pid string) string { + spath := RootTaskPath(module) + if len(pid) > 0 { + spath = path.Join(spath, pid) + } + return path.Join(spath, name) +} + +func GetRootParam(module, name, pid string) string { + param, err := fileutils2.FileGetContents(GetTaskParamPath(module, name, pid)) + if err != nil { + log.Errorln(err) + return "" + } + return strings.TrimSpace(param) +} + +func SetRootParam(module, name, value, pid string) bool { + if param := GetRootParam(module, name, pid); param != value { + fi, err := os.Open(GetTaskParamPath(module, name, pid)) + if err == nil { + _, err = fi.Write([]byte(value)) + if err != nil { + err = fi.Close() + } else { + log.Errorln(err) + } + } else { + log.Errorln(err) + } + + if err != nil { + if len(pid) == 0 { + pid = "root" + } + log.Errorf("fail to set %s to %s(%s): %s", name, value, pid, err) + return false + } + } + return true +} + +func CleanupNonexistPids(module string) { + var root = RootTaskPath(module) + files, err := ioutil.ReadDir(root) + if err != nil { + log.Errorf("GetTaskIds failed: %s", err) + return + } + ids := []string{} + for _, file := range files { + ids = append(ids, file.Name()) + } + re := regexp.MustCompile(`^\d+$`) + for _, pid := range ids { + if re.MatchString(pid) && fileutils2.IsDir(path.Join(root, pid)) { + if !fileutils2.Exists(path.Join("/proc", pid)) { + log.Infof("Cgroup clenup %s", pid) + if err := os.Remove(path.Join(root, pid)); err != nil { + log.Errorf("CleanupNonexistPids pid=%s error: %s", pid, err) + } + } + } + } +} + +func (c *CGroupTask) SetWeight(coreNum int) { + c.weight = float64(coreNum) / normalizeBase +} + +func (c *CGroupTask) SetHand(hand ICGroupTask) { + c.hand = hand +} + +func (c *CGroupTask) SetPid(pid string) { + c.pid = pid +} + +func (c *CGroupTask) Module() string { + return "" +} + +func (c *CGroupTask) GetWeight() float64 { + if c.weight < maxWeight { + return c.weight + } else { + return maxWeight + } +} + +func (c *CGroupTask) GetTaskIds() []string { + files, err := ioutil.ReadDir(fmt.Sprintf("/proc/%s/task", c.pid)) + if err != nil { + log.Errorf("GetTaskIds failed: %s", err) + return nil + } + ids := []string{} + for _, file := range files { + ids = append(ids, file.Name()) + } + return ids +} + +func (c *CGroupTask) TaskPath() string { + return path.Join(RootTaskPath(c.hand.Module()), c.pid) +} + +func (c *CGroupTask) taskIsExist() bool { + return fileutils2.Exists(c.TaskPath()) +} + +func (c *CGroupTask) createTask() bool { + if err := os.Mkdir(c.TaskPath(), os.ModePerm); err != nil { + log.Errorln(err) + return false + } + return true +} + +func (c *CGroupTask) GetParam(name string) string { + return GetRootParam(c.hand.Module(), name, c.pid) +} + +func (c *CGroupTask) MoveTasksToRoot() { + procs := c.GetParam(CGROUP_TASKS) + if len(procs) > 0 { + for _, proc := range strings.Split(procs, "\n") { + proc = strings.TrimSpace(proc) + if len(proc) > 0 { + c.PushPid(proc, true) + } + } + } +} + +func (c *CGroupTask) RemoveTask() bool { + if c.taskIsExist() { + c.MoveTasksToRoot() + log.Infof("Remove task path %s", c.TaskPath()) + if err := os.Remove(c.TaskPath()); err != nil { + log.Errorf("Remove task path failed %s", err) + return false + } + } + return true +} + +func (c *CGroupTask) GetStaticConfig() map[string]string { + return nil +} + +func (c *CGroupTask) GetConfig() map[string]string { + return nil +} + +func (c *CGroupTask) SetParam(name, value string) bool { + return SetRootParam(c.hand.Module(), name, value, c.pid) +} + +func (c *CGroupTask) SetParams(conf map[string]string) bool { + for k, v := range conf { + if !c.SetParam(k, v) { + log.Errorf("Fail to set %s/%s=%s for %s", c.hand.Module(), k, v, c.pid) + return false + } + } + return true +} + +func (c *CGroupTask) SetTask() bool { + if !c.taskIsExist() { + if !c.createTask() { + return false + } + } + conf := c.hand.GetStaticConfig() + if c.SetParams(conf) { + conf = c.hand.GetConfig() + if c.SetParams(conf) { + pids := c.GetTaskIds() + if len(pids) > 0 { + for _, pid := range pids { + c.PushPid(pid, false) + } + return true + } + } + } + return false +} + +func (c *CGroupTask) PushPid(pid string, isRoot bool) { + subdir := fmt.Sprintf("/proc/%s/task/%s", c.pid, pid) + if fi, err := os.Stat(subdir); err != nil { + log.Errorf("Fail to put pid in task %s", err) + return + } else if fi.Mode().IsDir() { + stat, err := fileutils2.FileGetContents(path.Join(subdir, "stat")) + if err != nil { + log.Errorf("Fail to put pid in task %s", err) + return + } + re := regexp.MustCompile(`\s+`) + data := re.Split(stat, -1) + if data[2] != "Z" { + if isRoot { + SetRootParam(c.hand.Module(), CGROUP_TASKS, pid, "") + } else { + c.SetParam(CGROUP_TASKS, pid) + } + } + } +} + +func (c *CGroupTask) init() bool { + if !CgroupIsMounted() { + if !fileutils2.Exists(cgroupsPath) { + if _, err := procutils.NewCommand("mkdir", "-p", cgroupsPath).Run(); err != nil { + log.Errorln(err) + } + } + if _, err := procutils.NewCommand("mount", "-t", "tmpfs", "-o", "uid=0,gid=0,mode=0755", + "cgroup", cgroupsPath).Run(); err != nil { + log.Errorln(err) + return false + } + } + file, err := os.Open("/proc/cgroups") + if err != nil { + log.Errorln(err) + return false + } + defer file.Close() + + re := regexp.MustCompile(`\s+`) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if line[0] != '#' { + parts := re.Split(line, -1) + module := parts[0] + if !ModuleIsMounted(module) { + moduleDir := path.Join(cgroupsPath, module) + if !fileutils2.Exists(moduleDir) { + if _, err := procutils.NewCommand("mkdir", moduleDir).Run(); err != nil { + log.Errorln(err) + return false + } + } + log.Errorln(module) + if _, err := procutils.NewCommand("mount", "-t", "cgroup", "-o", + module, module, moduleDir).Run(); err != nil { + log.Errorln(err) + return false + } + } + } + } + + if err := scanner.Err(); err != nil { + log.Errorln(err) + return false + } + + return true +} + +/** + * CGroupCPUTask + */ + +type CGroupCPUTask struct { + *CGroupTask +} + +const ( + CgroupsSharesWeight = 1024 + CPU_SHARES = "cpu.shares" +) + +func (c *CGroupCPUTask) Module() string { + return "cpu" +} + +func (c *CGroupCPUTask) GetConfig() map[string]string { + wt := int(CgroupsSharesWeight * c.GetWeight()) + return map[string]string{CPU_SHARES: fmt.Sprintf("%d", wt)} +} + +func (c *CGroupCPUTask) init() bool { + return SetRootParam(c.Module(), CPU_SHARES, + fmt.Sprintf("%d", CgroupsSharesWeight), "") +} + +func NewCGroupCPUTask(pid string, coreNum int) CGroupCPUTask { + cgroup := CGroupCPUTask{NewCGroupTask(pid, coreNum)} + cgroup.hand = &cgroup + return cgroup +} + +/** + * CGroupIOTask + */ + +type CGroupIOTask struct { + *CGroupTask +} + +const ( + IoWeightBase = 100 + IoWeightMax = 1000 + IoWeightMin = 100 + BLOCK_IO_WEIGHT = "blkio.weight" +) + +func (c *CGroupIOTask) Module() string { + return "blkio" +} + +func (c *CGroupIOTask) GetConfig() map[string]string { + wt := int(c.GetWeight() * IoWeightBase) + if wt > IoWeightMax { + wt = IoWeightMax + } else if wt < IoWeightMin { + wt = IoWeightMin + } + return map[string]string{BLOCK_IO_WEIGHT: fmt.Sprintf("%d", wt)} +} + +func (c *CGroupIOTask) init() bool { + return SetRootParam(c.Module(), BLOCK_IO_WEIGHT, fmt.Sprintf("%d", IoWeightMax), "") +} + +func NewCGroupIOTask(pid string, coreNum int) *CGroupIOTask { + return &CGroupIOTask{NewCGroupTask(pid, coreNum)} +} + +/** + * CGroupIOHardlimitTask + */ + +type CGroupIOHardlimitTask struct { + *CGroupIOTask + + cpuNum int + params map[string]int + devId string +} + +func (c *CGroupIOHardlimitTask) GetConfig() map[string]string { + config := make(map[string]string, 0) + for k, v := range c.params { + if v != 0 { + config[k] = fmt.Sprintf("%s %d", c.devId, v*c.cpuNum) + } + } + return config +} + +func NewCGroupIOHardlimitTask(pid string, mem int, params map[string]int, devId string) CGroupIOHardlimitTask { + return CGroupIOHardlimitTask{ + CGroupIOTask: NewCGroupIOTask(pid, 0), + cpuNum: mem, + params: params, + devId: devId, + } +} + +/** + * CGroupMemoryTask + */ + +type CGroupMemoryTask struct { + *CGroupTask +} + +const ( + root_swappiness = 60 + vm_swappiness = 0 + MEMORY_SWAPPINESS = "memory.swappiness" +) + +func (c *CGroupMemoryTask) Module() string { + return "memory" +} + +func (c *CGroupMemoryTask) GetConfig() map[string]string { + return map[string]string{MEMORY_SWAPPINESS: fmt.Sprintf("%d", vm_swappiness)} +} + +func NewCGroupMemoryTask(pid string, coreNum int) CGroupMemoryTask { + return CGroupMemoryTask{ + CGroupTask: NewCGroupTask(pid, coreNum), + } +} + +/** + * CGroupCPUSetTask + */ + +type CGroupCPUSetTask struct { + *CGroupTask + + cpuset string +} + +const ( + CPUSET_CPUS = "cpuset.cpus" + CPUSET_MEMS = "cpuset.mems" +) + +func (c *CGroupCPUSetTask) Module() string { + return "cpuset" +} + +func (c *CGroupCPUSetTask) GetStaticConfig() map[string]string { + return map[string]string{CPUSET_MEMS: GetRootParam(c.Module(), CPUSET_MEMS, "")} +} + +func (c *CGroupCPUSetTask) GetConfig() map[string]string { + return map[string]string{CPUSET_CPUS: c.cpuset} +} + +func NewCGroupCPUSetTask(pid string, coreNum int, cpuset string) CGroupCPUSetTask { + return CGroupCPUSetTask{ + CGroupTask: NewCGroupTask(pid, coreNum), + cpuset: cpuset, + } +} + +func Init() bool { + for _, hand := range []ICGroupTask{&CGroupTask{}, &CGroupCPUTask{}, &CGroupIOTask{}} { + if !hand.init() { + return false + } + } + return true +} + +func CgroupSet(pid string, coreNum int) bool { + tasks := []ICGroupTask{ + &CGroupCPUTask{&CGroupTask{}}, + &CGroupIOTask{&CGroupTask{}}, + &CGroupMemoryTask{&CGroupTask{}}, + } + for _, hand := range tasks { + hand.SetHand(hand) + hand.SetPid(pid) + hand.SetWeight(coreNum) + if !hand.SetTask() { + return false + } + } + return true +} + +func CgroupIoHardlimitSet( + pid string, coreNum int, + params map[string]int, devId string, +) bool { + cg := NewCGroupIOHardlimitTask(pid, coreNum, params, devId) + return cg.SetTask() +} + +func CgroupDestroy(pid string) bool { + tasks := []ICGroupTask{ + &CGroupCPUTask{&CGroupTask{}}, + &CGroupIOTask{&CGroupTask{}}, + &CGroupMemoryTask{&CGroupTask{}}, + &CGroupCPUSetTask{&CGroupTask{}, ""}, + &CGroupIOHardlimitTask{CGroupIOTask: &CGroupIOTask{&CGroupTask{}}}, + } + for _, hand := range tasks { + hand.SetHand(hand) + hand.SetPid(pid) + if !hand.RemoveTask() { + return false + } + } + return true +} + +func CgroupCleanAll() { + tasks := []ICGroupTask{ + &CGroupCPUTask{&CGroupTask{}}, + &CGroupIOTask{&CGroupTask{}}, + &CGroupMemoryTask{&CGroupTask{}}, + &CGroupCPUSetTask{CGroupTask: &CGroupTask{}}, + &CGroupIOHardlimitTask{CGroupIOTask: &CGroupIOTask{&CGroupTask{}}}, + } + for _, hand := range tasks { + hand.SetHand(hand) + CleanupNonexistPids(hand.Module()) + } +} diff --git a/pkg/util/cgrouputils/cgrouputils_test.go b/pkg/util/cgrouputils/cgrouputils_test.go new file mode 100644 index 0000000000..9295a4471e --- /dev/null +++ b/pkg/util/cgrouputils/cgrouputils_test.go @@ -0,0 +1,19 @@ +package cgrouputils + +import ( + "bufio" + "fmt" + "os" + "strings" + "testing" +) + +func TestCgroupSet(t *testing.T) { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Enter pid: ") + pid, _ := reader.ReadString('\n') + pid = strings.TrimSpace(pid) + t.Logf("Start %s cgroup set", pid) + CgroupSet(pid, 1) + CgroupCleanAll() +} diff --git a/pkg/util/cgrouputils/cpusetutils.go b/pkg/util/cgrouputils/cpusetutils.go new file mode 100644 index 0000000000..99317dc4f6 --- /dev/null +++ b/pkg/util/cgrouputils/cpusetutils.go @@ -0,0 +1,237 @@ +package cgrouputils + +import ( + "encoding/json" + "io/ioutil" + "regexp" + "strconv" + "sync" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/util/fileutils2" +) + +const ( + utilHistoryFile = "/tmp/util.history" + MAX_HISTORY_UTIL_COUNT = 5 +) + +var ( + rebalanceProcessesLock = sync.Mutex{} + rebalanceProcessesRunning = false + + utilHistory map[string][]float64 +) + +func RebalanceProcesses(pids []string) { + rebalanceProcessesLock.Lock() + if rebalanceProcessesRunning { + rebalanceProcessesLock.Unlock() + return + } else { + rebalanceProcessesRunning = true + rebalanceProcessesLock.Unlock() + } + + err := rebalanceProcesses(pids) + if err != nil { + log.Errorln("rebalance processes error: %s", err) + } + rebalanceProcessesRunning = false +} + +func rebalanceProcesses(pids []string) error { + FetchHistoryUtil() + + cpu, err := GetSystemCpu() + if err != nil { + log.Errorln(err) + return err + } + cpuCount := cpu.GetPhysicalNum() + if cpuCount <= 1 { + return nil + } + + info, err := GetProcessesCpuinfo(pids) + if err != nil { + return err + } + ret := ArrangeProcesses(info, cpuCount) + if len(ret) != 0 { + CommitProcessesCpuset(ret) + } + + SaveHistoryUtil() + return nil +} + +func CommitProcessesCpuset(cpus []CPULoad) { + for i, cpu := range cpus { + for _, proc := range cpu.Processes { + if proc.Cpuset == nil || *proc.Cpuset != i { + CommitProcessCpuset(proc, i) + } + } + } +} + +func CommitProcessCpuset(proc *ProcessCPUinfo, idx int) { + cpu, _ := GetSystemCpu() + sets := cpu.GetCpuset(idx) + if len(sets) > 0 { + cpuset := NewCGroupCPUSetTask(strconv.Itoa(proc.Pid), 0, sets) + cpuset.SetTask() + } +} + +func GetMaxLoadDiff(cpus []CPULoad) float64 { + var maxLd, minLd = -1.0, -1.0 + for _, cpu := range cpus { + wt := cpu.GetWeight() + if maxLd < 0 || maxLd < wt { + maxLd = wt + } + if minLd < 0 || minLd > wt { + minLd = wt + } + } + if minLd > 0 { + return (maxLd - minLd) / minLd + } else { + return 0.0 + } +} + +func GetMinLoadCpu(cpus []CPULoad, cpuset *int) *CPULoad { + var ( + mintWt = -1.0 + minCpu *CPULoad + ) + + for i := 0; i < len(cpus); i++ { + wt := cpus[i].GetWeight() + if mintWt < 0.0 || mintWt > wt || (mintWt == wt && cpuset != nil && *cpuset == i) { + mintWt = wt + minCpu = &cpus[i] + } + } + return minCpu +} + +func ArrangeProcesses(infos []*ProcessCPUinfo, cpuCount int) []CPULoad { + var ( + newProc = false + cpus = make([]CPULoad, cpuCount) + procs = CPULoad{} + ) + + for _, info := range infos { + procs.AddProcess(info) + if info.Cpuset != nil { + cpus[*info.Cpuset].AddProcess(info) + } else { + newProc = true + } + } + prevDiff := GetMaxLoadDiff(cpus) + if !newProc && prevDiff < 0.2 { + return nil + } + for _, c := range cpus { + c.Processes = nil + } + procs.Sort() + + for _, p := range procs.Processes { + mincpu := GetMinLoadCpu(cpus, p.Cpuset) + mincpu.AddProcess(p) + } + return cpus +} + +func GetProcessesCpuinfo(pids []string) ([]*ProcessCPUinfo, error) { + if len(pids) == 0 { + var err error + pids, err = GetAllPids() + if err != nil { + log.Errorln(err) + return nil, err + } + } + cpu, err := GetSystemCpu() + if err != nil { + log.Errorln(err) + return nil, err + } + coreCnt := len(cpu.DieList[0].CoreList) + + var ret = []*ProcessCPUinfo{} + for _, pid := range pids { + ipid, err := strconv.Atoi(pid) + if err != nil { + log.Errorln(err) + return nil, err + } + info, err := NewProcessCPUinfo(ipid) + if err != nil { + log.Errorln(err) + return nil, err + } + if info.Share != nil && *info.Share < coreCnt { + ret = append(ret, info) + } + } + return ret, nil +} + +func GetAllPids() ([]string, error) { + var pids = []string{} + files, err := ioutil.ReadDir("/proc") + if err != nil { + return nil, err + } + re := regexp.MustCompile(`^\d+$`) + for _, f := range files { + if re.MatchString(f.Name()) { + pids = append(pids, f.Name()) + } + } + return pids, nil +} + +func FetchHistoryUtil() map[string][]float64 { + if utilHistory == nil { + utilHistory = make(map[string][]float64) + if fileutils2.Exists(utilHistoryFile) { + contents, err := fileutils2.FileGetContents(utilHistoryFile) + if err != nil { + log.Errorf("FetchHistoryUtil error: %s", err) + return utilHistory + } + var objmap map[string]*json.RawMessage + if err := json.Unmarshal([]byte(contents), &objmap); err != nil { + log.Errorf("FetchHistoryUtil error: %s", err) + return utilHistory + } + for k, v := range objmap { + var s = []float64{} + if err := json.Unmarshal(*v, &s); err != nil { + log.Errorf("FetchHistoryUtil error: %s", err) + break + } + utilHistory[k] = s + } + } + } + return utilHistory +} + +func SaveHistoryUtil() { + content, err := json.Marshal(utilHistory) + if err != nil { + log.Errorf("SaveHistoryUtil error: %s", err) + } else { + fileutils2.FilePutContents(utilHistoryFile, string(content), false) + } +} diff --git a/pkg/util/cgrouputils/cpuutils.go b/pkg/util/cgrouputils/cpuutils.go new file mode 100644 index 0000000000..32396c9421 --- /dev/null +++ b/pkg/util/cgrouputils/cpuutils.go @@ -0,0 +1,258 @@ +package cgrouputils + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "github.com/shirou/gopsutil/process" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/util/fileutils2" +) + +var systemCpu *CPU + +type CPU struct { + DieList []*CPUDie +} + +func NewCPU() (*CPU, error) { + var cpu = new(CPU) + cpuinfo, err := fileutils2.FileGetContents("/proc/cpuinfo") + if err != nil { + return nil, err + } + + var core *CPUCore + for _, line := range strings.Split(cpuinfo, "\n") { + parts := strings.Split(line, ":") + if len(parts) == 2 { + if parts[0] == "processor" { + val, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, err + } + core = NewCPUCore(val) + cpu.AddCore(core) + } else if core != nil { + core.setInfo(parts[0], parts[1]) + } + } + } + return cpu, nil +} + +func (c *CPU) AddCore(core *CPUCore) { + for len(c.DieList) < core.PhysicalId { + c.DieList = append(c.DieList, NewCPUDie(len(c.DieList))) + } + c.DieList[core.PhysicalId].AddCore(core) +} + +func (c *CPU) GetCpuset(idx int) string { + if idx >= 0 && idx < len(c.DieList) { + return c.DieList[idx].GetCoreStr() + } else { + return "" + } +} + +func (c *CPU) GetPhysicalNum() int { + return len(c.DieList) +} + +func (c *CPU) GetPhysicalId(cstr string) int { + for _, d := range c.DieList { + if d.GetCoreStr() == cstr { + return d.Index + } + } + return -1 +} + +type CPUCore struct { + Index int + VendorId string + Mhz float64 + CacheSize string + PhysicalId int + CoreId int +} + +func NewCPUCore(index int) *CPUCore { + return &CPUCore{Index: index} +} + +func (c *CPUCore) setInfo(k, v string) { + switch k { + case "vendor_id": + c.VendorId = v + case "cpu MHz": + val, _ := strconv.ParseFloat(v, 64) + c.Mhz = val + case "cache size": + c.CacheSize = strings.Split(v, " ")[0] + case "physical id": + val, _ := strconv.Atoi(v) + c.PhysicalId = val + case "core id": + val, _ := strconv.Atoi(v) + c.CoreId = val + } +} + +type CPUDie struct { + Index int + CoreList []*CPUCore +} + +func (d *CPUDie) AddCore(core *CPUCore) { + d.CoreList = append(d.CoreList, core) +} + +func (d *CPUDie) GetCoreStr() string { + coreIdx := []string{} + for _, c := range d.CoreList { + coreIdx = append(coreIdx, strconv.Itoa(c.Index)) + } + sort.Slice(coreIdx, func(i int, j int) bool { return coreIdx[i] < coreIdx[j] }) + return strings.Join(coreIdx, ",") +} + +func NewCPUDie(index int) *CPUDie { + return &CPUDie{Index: index} +} + +func GetSystemCpu() (*CPU, error) { + if systemCpu == nil { + var err error + systemCpu, err = NewCPU() + if err != nil { + return nil, err + } + } + return systemCpu, nil +} + +func ParseCpusetStr(cpuset string) string { + var ( + idxList = make([]string, 0) + sets = strings.Split(cpuset, ",") + ) + + for _, idxstr := range sets { + if strings.Contains(idxstr, "-") { + ses := strings.Split(idxstr, "-") + start, _ := strconv.Atoi(ses[0]) + end, _ := strconv.Atoi(ses[1]) + for start < end { + idxList = append(idxList, strconv.Itoa(start)) + start += 1 + } + } else { + idxList = append(idxList, idxstr) + } + } + sort.Slice(idxList, func(i int, j int) bool { return idxList[i] < idxList[j] }) + return strings.Join(idxList, ",") +} + +type ProcessCPUinfo struct { + Pid int + Share *int + Cpuset *int + Util float64 + Weight float64 +} + +func Average(arr []float64) float64 { + var total = 0.0 + for _, a := range arr { + total += a + } + return total / float64(len(arr)) +} + +func GetProcessWeight(share int, util float64) float64 { + return float64(share) * (util*0.8 + 30) +} + +func NewProcessCPUinfo(pid int) (*ProcessCPUinfo, error) { + cpuinfo := new(ProcessCPUinfo) + cpuinfo.Pid = pid + spid := strconv.Itoa(pid) + + share := NewCGroupCPUTask(spid, 0).GetParam("cpu.shares") + ishare, err := strconv.Atoi(share) + if err != nil { + log.Errorln(err) + } else { + if ishare != 0 { + cpuinfo.Share = &ishare + proc, err := process.NewProcess(int32(pid)) + if err != nil { + log.Errorln(err) + return nil, err + } + util, err := proc.CPUPercent() + if err != nil { + log.Errorln(err) + return nil, err + } + util /= float64(ishare) + uHistory := FetchHistoryUtil() + + var utils = []float64{} + if _, ok := uHistory[spid]; ok { + utils = uHistory[spid] + } + utils = append(utils, util) + for len(utils) > MAX_HISTORY_UTIL_COUNT { + utils = utils[1:] + } + uHistory[spid] = utils + + cpuinfo.Util = Average(utils) + } + } + + cpuset := NewCGroupCPUSetTask(fmt.Sprintf("%s", pid), 0, "").GetParam("cpuset.cpus") + if len(cpuset) > 0 { + c, err := GetSystemCpu() + if err != nil { + log.Errorln(err) + } else { + icpuset := c.GetPhysicalId(ParseCpusetStr(cpuset)) + cpuinfo.Cpuset = &icpuset + } + } + + cpuinfo.Weight = GetProcessWeight(*cpuinfo.Share, cpuinfo.Util) + return cpuinfo, nil +} + +type CPULoad struct { + Processes []*ProcessCPUinfo +} + +func (c *CPULoad) AddProcess(proc *ProcessCPUinfo) { + if c.Processes == nil { + c.Processes = make([]*ProcessCPUinfo, 0) + } + c.Processes = append(c.Processes, proc) +} + +func (c *CPULoad) GetWeight() float64 { + var wt = 0.0 + for _, p := range c.Processes { + wt += p.Weight + } + return wt +} + +func (c *CPULoad) Sort() { + sort.Slice(c.Processes, func(i, j int) bool { + return c.Processes[i].Weight < c.Processes[j].Weight + }) +} diff --git a/pkg/util/cgrouputils/doc.go b/pkg/util/cgrouputils/doc.go new file mode 100644 index 0000000000..5819100146 --- /dev/null +++ b/pkg/util/cgrouputils/doc.go @@ -0,0 +1 @@ +package cgrouputils // import "yunion.io/x/onecloud/pkg/util/cgrouputils" diff --git a/pkg/util/dhcp/conn.go b/pkg/util/dhcp/conn.go new file mode 100644 index 0000000000..44a3930eb2 --- /dev/null +++ b/pkg/util/dhcp/conn.go @@ -0,0 +1,340 @@ +// Copyright 2016 Google Inc. +// +// 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 dhcp + +import ( + "errors" + "fmt" + "io" + "net" + "strconv" + "syscall" + "time" + + "golang.org/x/net/ipv4" +) + +// defined as a var so tests can override it. +var ( + dhcpClientPort = 68 +) + +// txType describes how a Packet should be sent on the wire. +type txType int + +// The various transmission strategies described in RFC 2131. "MUST", +// "MUST NOT", "SHOULD" and "MAY" are as specified in RFC 2119. +const ( + // Packet MUST be broadcast. + txBroadcast txType = iota + // Packet MUST be unicasted to port 67 of RelayAddr + txRelayAddr + // Packet MUST be unicasted to port 68 of ClientAddr + txClientAddr + // Packet SHOULD be unicasted to port 68 of YourAddr, with the + // link-layer destination explicitly set to HardwareAddr. You MUST + // NOT rely on ARP resolution to discover the link-layer + // destination address. + // + // Conn implementations that cannot explicitly set the link-layer + // destination address MAY instead broadcast the packet. + txHardwareAddr +) + +type conn interface { + io.Closer + Recv([]byte) (b []byte, addr *net.UDPAddr, ifidx int, err error) + Send(b []byte, addr *net.UDPAddr, ifidx int) error + SetReadDeadline(t time.Time) error + SetWriteDeadline(t time.Time) error +} + +// Conn is a DHCP-oriented packet socket. +// +// Multiple goroutines may invoke methods on a Conn simultaneously. +type Conn struct { + conn conn + ifIndex int +} + +// NewConn creates a Conn bound to the given UDP ip:port. +func NewConn(addr string) (*Conn, error) { + return newConn(addr, newPortableConn) +} + +func NewSocketConn(addr string) (*Conn, error) { + return newConn(addr, newSocketConn) +} + +func newConn(addr string, n func(net.IP, int) (conn, error)) (*Conn, error) { + if addr == "" { + addr = "0.0.0.0:67" + } + + ifIndex := 0 + udpAddr, err := net.ResolveUDPAddr("udp4", addr) + if err != nil { + return nil, err + } + if !udpAddr.IP.To4().Equal(net.IPv4zero) { + // Caller wants to listen only on one address. However, DHCP + // packets are frequently broadcast, so we can't just listen + // on the given address. Instead, we need to translate it to + // an interface, and then filter incoming packets based on + // their received interface. + ifIndex, err = ipToIfindex(udpAddr.IP) + if err != nil { + return nil, err + } + } + + c, err := n(udpAddr.IP, udpAddr.Port) + if err != nil { + return nil, err + } + return &Conn{ + conn: c, + ifIndex: ifIndex, + }, nil +} + +func ipToIfindex(ip net.IP) (int, error) { + intfs, err := net.Interfaces() + if err != nil { + return 0, err + } + for _, intf := range intfs { + addrs, err := intf.Addrs() + if err != nil { + return 0, err + } + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.Equal(ip) { + return intf.Index, nil + } + } + } + } + return 0, fmt.Errorf("IP %s not found on any local interface", ip) +} + +// Close closes the DHCP socket. +// Any blocked Read or Write operations will be unblocked and return errors. +func (c *Conn) Close() error { + return c.conn.Close() +} + +// RecvDHCP reads a Packet from the connection. It returns the +// packet and the interface it was received on. +func (c *Conn) RecvDHCP() (Packet, *net.UDPAddr, *net.Interface, error) { + var buf [1500]byte + b, addr, _, err := c.conn.Recv(buf[:]) + if err != nil { + return nil, nil, nil, err + } + /*if c.ifIndex != 0 && ifidx != c.ifIndex { + log.Errorf("======= ifIndex continue, c.ifIndex: %d, ifidx: %d", c.ifIndex, ifidx) + continue + }*/ + pkt := Unmarshal(b) + // intf, err := net.InterfaceByIndex(ifidx) + // if err != nil { + // return nil, nil, nil, err + // } + + // TODO: possibly more validation that the source lines up + // with what the packet says. + return pkt, addr, nil, nil +} + +// SendDHCP sends pkt. The precise transmission mechanism depends +// on pkt.txType(). intf should be the net.Interface returned by +// RecvDHCP if responding to a DHCP client, or the interface for +// which configuration is desired if acting as a client. +func (c *Conn) SendDHCP(pkt Packet, addr *net.UDPAddr, intf *net.Interface) error { + b := pkt.Marshal() + + ipStr, portStr, err := net.SplitHostPort(addr.String()) + if err != nil { + return err + } + + if net.ParseIP(ipStr).Equal(net.IPv4zero) || pkt.txType() == txBroadcast { + port, _ := strconv.Atoi(portStr) + addr = &net.UDPAddr{IP: net.IPv4bcast, Port: port} + } + return c.conn.Send(b, addr, 0) + + /* + switch pkt.txType() { + case txBroadcast, txHardwareAddr: + addr := net.UDPAddr{ + IP: net.IPv4bcast, + Port: dhcpClientPort, + } + return c.conn.Send(b, &addr, intf.Index) + case txRelayAddr: + addr := net.UDPAddr{ + IP: pkt.RelayAddr(), + Port: dhcpClientPort, + } + log.Errorf("===============relay type pkt, addr: %#v", addr) + return c.conn.Send(b, &addr, 0) + case txClientAddr: + addr := net.UDPAddr{ + IP: pkt.CIAddr(), + Port: dhcpClientPort, + } + return c.conn.Send(b, &addr, 0) + default: + return errors.New("unknown TX type for packet") + }*/ +} + +// SetReadDeadline sets the deadline for future Read calls. If the +// deadline is reached, Read will fail with a timeout (see net.Error) +// instead of blocking. A zero value for t means Read will not time +// out. +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +// SetWriteDeadline sets the deadline for future Write calls. If the +// deadline is reached, Write will fail with a timeout (see net.Error) +// instead of blocking. A zero value for t means Write will not time +// out. +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +type portableConn struct { + conn *ipv4.PacketConn +} + +func newPortableConn(_ net.IP, port int) (conn, error) { + c, err := net.ListenPacket("udp4", fmt.Sprintf(":%d", port)) + if err != nil { + return nil, err + } + l := ipv4.NewPacketConn(c) + if err = l.SetControlMessage(ipv4.FlagInterface, true); err != nil { + l.Close() + return nil, err + } + return &portableConn{l}, nil +} + +func (c *portableConn) Close() error { + return c.conn.Close() +} + +func (c *portableConn) Recv(b []byte) (rb []byte, addr *net.UDPAddr, ifidx int, err error) { + n, cm, a, err := c.conn.ReadFrom(b) + if err != nil { + return nil, nil, 0, err + } + return b[:n], a.(*net.UDPAddr), cm.IfIndex, nil +} + +func (c *portableConn) Send(b []byte, addr *net.UDPAddr, ifidx int) error { + if ifidx <= 0 { + _, err := c.conn.WriteTo(b, nil, addr) + return err + } + cm := ipv4.ControlMessage{ + IfIndex: ifidx, + } + _, err := c.conn.WriteTo(b, &cm, addr) + return err +} + +func (c *portableConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +func (c *portableConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +type socketConn struct { + sock int +} + +func newSocketConn(addr net.IP, port int) (conn, error) { + sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0) + if err != nil { + return nil, err + } + if err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { + return nil, err + } + if err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil { + return nil, err + } + byteAddr := [4]byte{} + copy(byteAddr[:], addr.To4()[:4]) + lsa := &syscall.SockaddrInet4{ + Port: port, + Addr: byteAddr, + } + if err = syscall.Bind(sock, lsa); err != nil { + return nil, err + } + if err = syscall.SetNonblock(sock, false); err != nil { + return nil, err + } + return &socketConn{sock}, nil +} + +func (s *socketConn) Close() error { + return syscall.Close(s.sock) +} + +func (s *socketConn) Recv(b []byte) (rb []byte, addr *net.UDPAddr, ifidx int, err error) { + n, a, err := syscall.Recvfrom(s.sock, b, 0) + if err != nil { + return nil, nil, 0, err + } + if addr, ok := a.(*syscall.SockaddrInet4); !ok { + return nil, nil, 0, errors.New("Recvfrom recevice address is not famliy Inet4") + } else { + ip := net.IP{addr.Addr[0], addr.Addr[1], addr.Addr[2], addr.Addr[3]} + udpAddr := &net.UDPAddr{ + IP: ip, + Port: addr.Port, + } + // there is no interface index info + return b[:n], udpAddr, 0, nil + } +} + +func (s *socketConn) Send(b []byte, addr *net.UDPAddr, ifidx int) error { + destIp := [4]byte{} + copy(destIp[:], addr.IP.To4()[:4]) + destAddr := &syscall.SockaddrInet4{ + Addr: destIp, + Port: addr.Port, + } + return syscall.Sendto(s.sock, b, 0, destAddr) +} + +func (s *socketConn) SetReadDeadline(t time.Time) error { + return errors.New("Not Implement") +} + +func (s *socketConn) SetWriteDeadline(t time.Time) error { + return errors.New("Not Implement") +} diff --git a/pkg/util/dhcp/conn_linux.go b/pkg/util/dhcp/conn_linux.go new file mode 100644 index 0000000000..23da403aef --- /dev/null +++ b/pkg/util/dhcp/conn_linux.go @@ -0,0 +1,140 @@ +// Copyright 2016 Google Inc. +// +// 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. + +//+build linux + +package dhcp + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "time" + + "golang.org/x/net/bpf" + "golang.org/x/net/ipv4" +) + +type linuxConn struct { + port uint16 + conn *ipv4.RawConn +} + +// NewSnooperConn creates a Conn that listens on the given UDP ip:port. +// +// Unlike NewConn, NewSnooperConn does not bind to the ip:port, +// enabling the Conn to coexist with other services on the machine. +func NewSnooperConn(addr string) (*Conn, error) { + return newConn(addr, newLinuxConn) +} + +func newLinuxConn(_ net.IP, port int) (conn, error) { + if port == 0 { + return nil, errors.New("must specify a listen port") + } + + filter, err := bpf.Assemble([]bpf.Instruction{ + // Load IPv4 packet length + bpf.LoadMemShift{Off: 0}, + // Get UDP dport + bpf.LoadIndirect{Off: 2, Size: 2}, + // Correct dport? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipFalse: 1}, + // Accept + bpf.RetConstant{Val: 1500}, + // Ignore + bpf.RetConstant{Val: 0}, + }) + if err != nil { + return nil, err + } + + c, err := net.ListenPacket("ip4:17", "0.0.0.0") + if err != nil { + return nil, err + } + r, err := ipv4.NewRawConn(c) + if err != nil { + c.Close() + return nil, err + } + if err = r.SetControlMessage(ipv4.FlagInterface, true); err != nil { + c.Close() + return nil, fmt.Errorf("setting packet filter: %s", err) + } + if err = r.SetBPF(filter); err != nil { + c.Close() + return nil, fmt.Errorf("setting packet filter: %s", err) + } + + ret := &linuxConn{ + port: uint16(port), + conn: r, + } + return ret, nil +} + +func (c *linuxConn) Close() error { + return c.conn.Close() +} + +func (c *linuxConn) Recv(b []byte) (rb []byte, addr *net.UDPAddr, ifidx int, err error) { + hdr, p, cm, err := c.conn.ReadFrom(b) + if err != nil { + return nil, nil, 0, err + } + if len(p) < 8 { + return nil, nil, 0, errors.New("not a UDP packet, too short") + } + sport := int(binary.BigEndian.Uint16(p[:2])) + return p[8:], &net.UDPAddr{IP: hdr.Src, Port: sport}, cm.IfIndex, nil +} + +func (c *linuxConn) Send(b []byte, addr *net.UDPAddr, ifidx int) error { + raw := make([]byte, 8+len(b)) + // src port + binary.BigEndian.PutUint16(raw[:2], c.port) + // dst port + binary.BigEndian.PutUint16(raw[2:4], uint16(addr.Port)) + // length + binary.BigEndian.PutUint16(raw[4:6], uint16(8+len(b))) + copy(raw[8:], b) + + hdr := ipv4.Header{ + Version: 4, + Len: ipv4.HeaderLen, + TOS: 0xc0, // DSCP CS6 (Network Control) + TotalLen: ipv4.HeaderLen + 8 + len(b), + TTL: 64, + Protocol: 17, + Dst: addr.IP, + } + + if ifidx > 0 { + cm := ipv4.ControlMessage{ + IfIndex: ifidx, + } + return c.conn.WriteTo(&hdr, raw, &cm) + } + return c.conn.WriteTo(&hdr, raw, nil) +} + +func (c *linuxConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +func (c *linuxConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} diff --git a/pkg/util/dhcp/conn_test.go b/pkg/util/dhcp/conn_test.go new file mode 100644 index 0000000000..9c1e59a813 --- /dev/null +++ b/pkg/util/dhcp/conn_test.go @@ -0,0 +1,132 @@ +// Copyright 2016 Google Inc. +// +// 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 dhcp + +import ( + "net" + "reflect" + "testing" + "time" +) + +func testConn(t *testing.T, impl conn, addr string) { + c := &Conn{impl, 0} + + s, err := net.Dial("udp4", addr) + if err != nil { + t.Fatal(err) + } + + mac, err := net.ParseMAC("ce:e7:7b:ef:45:f7") + if err != nil { + t.Fatal(err) + } + + p := &Packet{ + Type: MsgDiscover, + TransactionID: []byte("1234"), + Broadcast: true, + HardwareAddr: mac, + } + bs, err := p.Marshal() + if err != nil { + t.Fatalf("marshaling packet: %s", err) + } + // Unmarshal the packet again, to smooth out representation + // differences (e.g. nil IP vs. IP set to 0.0.0.0). + p, err = Unmarshal(bs) + if err != nil { + t.Fatal(err) + } + + go func() { + s.Write(bs) + }() + if err = c.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Fatal(err) + } + rpkt, intf, err := c.RecvDHCP() + if err != nil { + t.Fatalf("reading DHCP packet: %s", err) + } + if !reflect.DeepEqual(p, rpkt) { + t.Fatalf("DHCP packet not the same as when it was sent") + } + + // Test writing + p.ClientAddr = net.IPv4(127, 0, 0, 1) + dhcpClientPort = s.LocalAddr().(*net.UDPAddr).Port + bs2, err := p.Marshal() + if err != nil { + t.Fatalf("marshaling packet: %s", err) + } + // Unmarshal the packet again, to smooth out representation + // differences (e.g. nil IP vs. IP set to 0.0.0.0). + p, err = Unmarshal(bs2) + if err != nil { + t.Fatal(err) + } + defer func() { dhcpClientPort = 68 }() + + ch := make(chan *Packet, 1) + go func() { + s.SetReadDeadline(time.Now().Add(time.Second)) + var buf [1500]byte + n, err := s.Read(buf[:]) + if err != nil { + t.Errorf("reading DHCP packet sent by conn_linux: %s", err) + ch <- nil + return + } + pkt, err := Unmarshal(buf[:n]) + if err != nil { + t.Errorf("decoding DHCP packet: %s", err) + ch <- nil + return + } + ch <- pkt + }() + + if err = c.SendDHCP(p, intf); err != nil { + t.Fatalf("sending DHCP packet: %s", err) + } + + rpkt = <-ch + if rpkt == nil { + t.FailNow() + } + if !reflect.DeepEqual(p, rpkt) { + t.Fatalf("DHCP packet not the same as when it was sent") + } +} + +func TestPortableConn(t *testing.T) { + // Use a listener to grab a free port, but we don't use it beyond + // that. + l, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + port := l.LocalAddr().(*net.UDPAddr).Port + addr := l.LocalAddr().String() + l.Close() + + c, err := newPortableConn(port) + if err != nil { + t.Fatalf("creating the conn: %s", err) + } + + testConn(t, c, addr) +} diff --git a/pkg/util/dhcp/conn_unsupported.go b/pkg/util/dhcp/conn_unsupported.go new file mode 100644 index 0000000000..569a28942c --- /dev/null +++ b/pkg/util/dhcp/conn_unsupported.go @@ -0,0 +1,27 @@ +// Copyright 2016 Google Inc. +// +// 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. + +//+build !linux + +package dhcp + +import "errors" + +// NewSnooperConn creates a Conn that listens on the given UDP ip:port. +// +// Unlike NewConn, NewSnooperConn does not bind to the ip:port, +// enabling the Conn to coexist with other services on the machine. +func NewSnooperConn(addr string) (*Conn, error) { + return nil, errors.New("snooper Conns not supported on this OS") +} diff --git a/pkg/util/dhcp/doc.go b/pkg/util/dhcp/doc.go new file mode 100644 index 0000000000..113b395e54 --- /dev/null +++ b/pkg/util/dhcp/doc.go @@ -0,0 +1,16 @@ +// Copyright 2016 Google Inc. +// +// 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 dhcp provides building blocks for DHCP clients and servers. +package dhcp // import "yunion.io/x/onecloud/pkg/util/dhcp" diff --git a/pkg/util/dhcp/helpers.go b/pkg/util/dhcp/helpers.go new file mode 100644 index 0000000000..c6570d45bb --- /dev/null +++ b/pkg/util/dhcp/helpers.go @@ -0,0 +1,166 @@ +package dhcp + +import ( + "encoding/binary" + "fmt" + "net" + "strconv" + "strings" + "time" + + "yunion.io/x/log" +) + +const ( + PXECLIENT = "PXEClient" + + OptClasslessRouteLin OptionCode = OptionClasslessRouteFormat //Classless Static Route Option + OptClasslessRouteWin OptionCode = 249 +) + +type ResponseConfig struct { + OsName string + ServerIP net.IP // OptServerIdentifier 54 + ClientIP net.IP + Gateway net.IP // OptRouters 3 + Domain string // OptDomainName 15 + LeaseTime time.Duration // OptLeaseTime 51 + RenewalTime time.Duration // OptRenewalTime 58 + BroadcastAddr net.IP // OptBroadcastAddr 28 + Hostname string // OptHostname 12 + SubnetMask net.IP // OptSubnetMask 1 + DNSServer net.IP // OptDNSServers + Routes [][]string // TODO: 249 for windows, 121 for linux + + // TFTP config + BootServer string + BootFile string + BootBlock uint16 +} + +func (conf ResponseConfig) GetHostname() string { + hostname := conf.Hostname + if conf.Domain != "" { + hostname = fmt.Sprintf("%s.%s", hostname, conf.Domain) + } + return hostname +} + +func GetOptIP(ip net.IP) []byte { + return []byte(ip.To4()) +} + +func GetOptTime(d time.Duration) []byte { + timeBytes := make([]byte, 4) + binary.BigEndian.PutUint32(timeBytes, uint32(d/time.Second)) + return timeBytes +} + +func GetClasslessRoutePack(route []string) []byte { + var snet, gw = route[0], route[1] + tmp := strings.Split(snet, "/") + netaddr := net.ParseIP(tmp[0]) + masklen, _ := strconv.Atoi(tmp[1]) + netlen := masklen / 8 + if masklen%8 > 0 { + netlen += 1 + } + if netlen < 4 { + netaddr = netaddr[0:netlen] + } + gwaddr := net.ParseIP(gw) + + res := []byte{byte(masklen)} + res = append(res, []byte(netaddr.To4())...) + return append(res, []byte(gwaddr.To4())...) +} + +func MakeReplyPacket(pkt Packet, conf *ResponseConfig) (Packet, error) { + msgType := Offer + if pkt.Type() == Request { + reqAddr, _ := pkt.ParseOptions().IP(OptionRequestedIPAddress) + if reqAddr != nil && !conf.ClientIP.Equal(reqAddr) { + msgType = NAK + } else { + msgType = ACK + } + } + return makeDHCPReplyPacket(pkt, conf, msgType), nil +} + +func getPacketVendorClassId(pkt Packet) string { + bs := pkt.ParseOptions()[OptionVendorClassIdentifier] + vendorClsId := string(bs) + return vendorClsId +} + +func makeDHCPReplyPacket(req Packet, conf *ResponseConfig, msgType MessageType) Packet { + if conf.OsName == "" { + if vendorClsId := getPacketVendorClassId(req); vendorClsId != "" && strings.HasPrefix(vendorClsId, "MSFT ") { + conf.OsName = "win" + } + } + + opts := make([]Option, 0) + + if conf.SubnetMask != nil { + opts = append(opts, Option{OptionSubnetMask, GetOptIP(conf.SubnetMask)}) + } + if conf.Gateway != nil { + opts = append(opts, Option{OptionRouter, GetOptIP(conf.Gateway)}) + } + if conf.Domain != "" { + opts = append(opts, Option{OptionDomainName, []byte(conf.Domain)}) + } + if conf.BroadcastAddr != nil { + opts = append(opts, Option{OptionBroadcastAddress, GetOptIP(conf.BroadcastAddr)}) + } + if conf.Hostname != "" { + opts = append(opts, Option{OptionHostName, []byte(conf.GetHostname())}) + } + if conf.DNSServer != nil { + opts = append(opts, Option{OptionDomainNameServer, GetOptIP(conf.DNSServer)}) + } + resp := ReplyPacket(req, msgType, conf.ServerIP, conf.ClientIP, conf.LeaseTime, opts) + if conf.BootServer != "" { + //resp.Options[OptOverload] = []byte{3} + resp.SetSIAddr(net.ParseIP(conf.BootServer)) + resp.AddOption(OptionTFTPServerName, []byte(fmt.Sprintf("%s\x00", conf.BootServer))) + } + if conf.BootFile != "" { + resp.AddOption(OptionBootFileName, []byte(fmt.Sprintf("%s\x00", conf.BootFile))) + sz := make([]byte, 2) + binary.BigEndian.PutUint16(sz, conf.BootBlock) + resp.AddOption(OptionBootFileSize, sz) + } + //if bs, _ := req.ParseOptions().Bytes(OptionClientMachineIdentifier); bs != nil { + //resp.AddOption(OptionClientMachineIdentifier, bs) + //} + if conf.RenewalTime > 0 { + resp.AddOption(OptionRenewalTimeValue, GetOptTime(conf.RenewalTime)) + } + if conf.Routes != nil { + var optCode = OptClasslessRouteLin + if strings.HasPrefix(strings.ToLower(conf.OsName), "win") { + optCode = OptClasslessRouteWin + } + for _, route := range conf.Routes { + routeBytes := GetClasslessRoutePack(route) + resp.AddOption(optCode, routeBytes) + } + } + return resp +} + +func IsPXERequest(pkt Packet) bool { + //if pkt.Type != MsgDiscover { + //log.Warningf("packet is %s, not %s", pkt.Type, MsgDiscover) + //return false + //} + + if pkt.GetOptionValue(OptionClientArchitecture) == nil { + log.Debugf("not a PXE boot request (missing option 93)") + return false + } + return true +} diff --git a/pkg/util/dhcp/packet.go b/pkg/util/dhcp/packet.go new file mode 100644 index 0000000000..8368cd61bb --- /dev/null +++ b/pkg/util/dhcp/packet.go @@ -0,0 +1,472 @@ +package dhcp + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "time" +) + +// Option is a DHCP option. +type Option struct { + Code OptionCode + Value []byte +} + +type OptionCode byte +type OpCode byte +type MessageType byte // Option 53 + +// A DHCP packet +type Packet []byte + +func (p Packet) OpCode() OpCode { return OpCode(p[0]) } +func (p Packet) HType() byte { return p[1] } +func (p Packet) HLen() byte { return p[2] } +func (p Packet) Hops() byte { return p[3] } +func (p Packet) XId() []byte { return p[4:8] } +func (p Packet) TransactionID() string { return string(p.XId()) } +func (p Packet) Secs() []byte { return p[8:10] } // Never Used? +func (p Packet) Flags() []byte { return p[10:12] } +func (p Packet) CIAddr() net.IP { return net.IP(p[12:16]) } // Client's current IP address (it will respond to ARP for this IP) +func (p Packet) YIAddr() net.IP { return net.IP(p[16:20]) } // Client IP address offered/assigned by server +func (p Packet) SIAddr() net.IP { return net.IP(p[20:24]) } // Responding server's IP address +func (p Packet) GIAddr() net.IP { return net.IP(p[24:28]) } // IP address of DHCP relay agent, if an agent forwarded the request +func (p Packet) CHAddr() net.HardwareAddr { + hLen := p.HLen() + if hLen > 16 { // Prevent chaddr exceeding p boundary + hLen = 16 + } + return net.HardwareAddr(p[28 : 28+hLen]) // max endPos 44 +} +func (p Packet) RelayAddr() net.IP { + return p.GIAddr() +} + +func Unmarshal(b []byte) Packet { + p := Packet(b) + return p +} + +func (p Packet) Marshal() []byte { + return p +} + +func (p Packet) Type() MessageType { + return MessageType(p.ParseOptions()[OptionDHCPMessageType][0]) +} + +// 192 bytes of zeros BOOTP legacy + +// BOOTP legacy +func (p Packet) SName() []byte { return trimNull(p[44:108]) } + +// BOOTP legacy +func (p Packet) File() []byte { return trimNull(p[108:236]) } + +func trimNull(d []byte) []byte { + for i, v := range d { + if v == 0 { + return d[:i] + } + } + return d +} + +func (p Packet) Cookie() []byte { return p[236:240] } +func (p Packet) Options() []byte { + if len(p) > 240 { + return p[240:] + } + return nil +} + +func (p Packet) Broadcast() bool { return p.Flags()[0] > 127 } + +func (p Packet) SetBroadcast(broadcast bool) { + if p.Broadcast() != broadcast { + p.Flags()[0] ^= 128 + } +} + +func (p Packet) SetOpCode(c OpCode) { p[0] = byte(c) } +func (p Packet) SetCHAddr(a net.HardwareAddr) { + copy(p[28:44], a) + p[2] = byte(len(a)) +} +func (p Packet) SetHType(hType byte) { p[1] = hType } +func (p Packet) SetCookie(cookie []byte) { copy(p.Cookie(), cookie) } +func (p Packet) SetHops(hops byte) { p[3] = hops } +func (p Packet) SetXId(xId []byte) { copy(p.XId(), xId) } +func (p Packet) SetSecs(secs []byte) { copy(p.Secs(), secs) } +func (p Packet) SetFlags(flags []byte) { copy(p.Flags(), flags) } +func (p Packet) SetCIAddr(ip net.IP) { copy(p.CIAddr(), ip.To4()) } +func (p Packet) SetYIAddr(ip net.IP) { copy(p.YIAddr(), ip.To4()) } +func (p Packet) SetSIAddr(ip net.IP) { copy(p.SIAddr(), ip.To4()) } +func (p Packet) SetGIAddr(ip net.IP) { copy(p.GIAddr(), ip.To4()) } + +// BOOTP legacy +func (p Packet) SetSName(sName []byte) { + copy(p[44:108], sName) + if len(sName) < 64 { + p[44+len(sName)] = 0 + } +} + +// BOOTP legacy +func (p Packet) SetFile(file []byte) { + copy(p[108:236], file) + if len(file) < 128 { + p[108+len(file)] = 0 + } +} + +func (p Packet) GetOptionValue(code OptionCode) []byte { + return p.ParseOptions()[code] +} + +// Map of DHCP options +type Options map[OptionCode][]byte + +var ( + ErrOptionWrongSize = errors.New("option value is the wrong size") +) + +func (o Options) Bytes(code OptionCode) ([]byte, error) { + bs := o[code] + if bs == nil { + return nil, fmt.Errorf("option %d not found", code) + } + return bs, nil +} + +func (o Options) String(n OptionCode) (string, error) { + bs, err := o.Bytes(n) + if err != nil { + return "", err + } + return string(bs), err +} + +func (o Options) Byte(n OptionCode) (byte, error) { + bs, err := o.Bytes(n) + if err != nil { + return 0, err + } + if len(bs) != 1 { + return 0, fmt.Errorf("option value %d is the wrong size", n) + } + return bs[0], nil +} + +func (o Options) Uint16(n OptionCode) (uint16, error) { + bs, err := o.Bytes(n) + if err != nil { + return 0, err + } + if len(bs) != 2 { + return 0, ErrOptionWrongSize + } + return binary.BigEndian.Uint16(bs), nil +} + +func (o Options) Uint32(n OptionCode) (uint32, error) { + bs, err := o.Bytes(n) + if err != nil { + return 0, err + } + if len(bs) != 4 { + return 0, ErrOptionWrongSize + } + return binary.BigEndian.Uint32(bs), nil +} + +func (o Options) IPs(n OptionCode) ([]net.IP, error) { + bs, err := o.Bytes(n) + if err != nil { + return nil, err + } + if len(bs) < 4 || len(bs)%4 != 0 { + return nil, ErrOptionWrongSize + } + ret := make([]net.IP, 0, len(bs)/4) + for i := 0; i < len(bs); i += 4 { + ret = append(ret, net.IP(bs[i:i+4])) + } + return ret, nil +} + +func (o Options) IP(n OptionCode) (net.IP, error) { + ips, err := o.IPs(n) + if err != nil { + return nil, err + } + if len(ips) != 1 { + return nil, ErrOptionWrongSize + } + return ips[0], nil +} + +func (o Options) IPMask(n OptionCode) (net.IPMask, error) { + bs := o[n] + if bs == nil { + return nil, fmt.Errorf("option %d not found", n) + } + if len(bs) != 4 { + return nil, fmt.Errorf("option %d is the wrong size for an IPMask", n) + } + return net.IPMask(bs), nil +} + +// Parses the packet's options into an Options map +func (p Packet) ParseOptions() Options { + opts := p.Options() + options := make(Options, 10) + for len(opts) >= 2 && OptionCode(opts[0]) != End { + if OptionCode(opts[0]) == Pad { + opts = opts[1:] + continue + } + size := int(opts[1]) + if len(opts) < 2+size { + break + } + options[OptionCode(opts[0])] = opts[2 : 2+size] + opts = opts[2+size:] + } + return options +} + +func NewPacket(opCode OpCode) Packet { + var magic = []byte{99, 130, 83, 99} + p := make(Packet, 241) + p.SetOpCode(opCode) + p.SetHType(1) // Ethernet + p.SetCookie(magic) + p[240] = byte(End) + return p +} + +// Appends a DHCP option to the end of a packet +func (p *Packet) AddOption(o OptionCode, value []byte) { + *p = append((*p)[:len(*p)-1], []byte{byte(o), byte(len(value))}...) // Strip off End, Add OptionCode and Length + *p = append(*p, value...) // Add Option Value + *p = append(*p, byte(End)) // Add on new End +} + +// Removes all options from packet. +func (p *Packet) StripOptions() { + *p = append((*p)[:240], byte(End)) +} + +// Creates a request packet that a Client would send to a server. +func RequestPacket(mt MessageType, chAddr net.HardwareAddr, cIAddr net.IP, xId []byte, broadcast bool, options []Option) Packet { + p := NewPacket(BootRequest) + p.SetCHAddr(chAddr) + p.SetXId(xId) + if cIAddr != nil { + p.SetCIAddr(cIAddr) + } + p.SetBroadcast(broadcast) + p.AddOption(OptionDHCPMessageType, []byte{byte(mt)}) + for _, o := range options { + p.AddOption(o.Code, o.Value) + } + p.PadToMinSize() + return p +} + +// ReplyPacket creates a reply packet that a Server would send to a client. +// It uses the req Packet param to copy across common/necessary fields to +// associate the reply the request. +func ReplyPacket(req Packet, mt MessageType, serverId, yIAddr net.IP, leaseDuration time.Duration, options []Option) Packet { + p := NewPacket(BootReply) + p.SetXId(req.XId()) + p.SetFlags(req.Flags()) + p.SetYIAddr(yIAddr) + p.SetGIAddr(req.GIAddr()) + p.SetCHAddr(req.CHAddr()) + p.AddOption(OptionDHCPMessageType, []byte{byte(mt)}) + p.AddOption(OptionServerIdentifier, []byte(serverId.To4())) + if leaseDuration > 0 { + p.AddOption(OptionIPAddressLeaseTime, GetOptTime(leaseDuration)) + } + for _, o := range options { + p.AddOption(o.Code, o.Value) + } + p.PadToMinSize() + return p +} + +// PadToMinSize pads a packet so that when sent over UDP, the entire packet, +// is 300 bytes (BOOTP min), to be compatible with really old devices. +var padder [272]byte + +func (p *Packet) PadToMinSize() { + if n := len(*p); n < 272 { + *p = append(*p, padder[:272-n]...) + } +} + +// decides how to send Packet on the wire, based on its field values. +// +// This implements the transmission decision process in section 4.1 of +// RFC 2131. +func (p *Packet) txType() txType { + switch { + case p.GIAddr() != nil && p.GIAddr().IsGlobalUnicast(): + return txRelayAddr + case p.Type() == NAK: + return txBroadcast + case p.CIAddr() != nil && (p.CIAddr().IsGlobalUnicast() || p.CIAddr().IsLoopback()): + return txClientAddr + case p.Broadcast(): + return txBroadcast + default: + return txHardwareAddr + } +} + +//go:generate stringer -type=OpCode + +// OpCodes +const ( + BootRequest OpCode = 1 // From Client + BootReply OpCode = 2 // From Server +) + +//go:generate stringer -type=MessageType + +// DHCP Message Type 53 +const ( + Discover MessageType = 1 // Broadcast Packet From Client - Can I have an IP? + Offer MessageType = 2 // Broadcast From Server - Here's an IP + Request MessageType = 3 // Broadcast From Client - I'll take that IP (Also start for renewals) + Decline MessageType = 4 // Broadcast From Client - Sorry I can't use that IP + ACK MessageType = 5 // From Server, Yes you can have that IP + NAK MessageType = 6 // From Server, No you cannot have that IP + Release MessageType = 7 // From Client, I don't need that IP anymore + Inform MessageType = 8 // From Client, I have this IP and there's nothing you can do about it +) + +//go:generate stringer -type=OptionCode + +// DHCP Options +const ( + End OptionCode = 255 + Pad OptionCode = 0 + OptionSubnetMask OptionCode = 1 + OptionTimeOffset OptionCode = 2 + OptionRouter OptionCode = 3 + OptionTimeServer OptionCode = 4 + OptionNameServer OptionCode = 5 + OptionDomainNameServer OptionCode = 6 + OptionLogServer OptionCode = 7 + OptionCookieServer OptionCode = 8 + OptionLPRServer OptionCode = 9 + OptionImpressServer OptionCode = 10 + OptionResourceLocationServer OptionCode = 11 + OptionHostName OptionCode = 12 + OptionBootFileSize OptionCode = 13 + OptionMeritDumpFile OptionCode = 14 + OptionDomainName OptionCode = 15 + OptionSwapServer OptionCode = 16 + OptionRootPath OptionCode = 17 + OptionExtensionsPath OptionCode = 18 + + // IP Layer Parameters per Host + OptionIPForwardingEnableDisable OptionCode = 19 + OptionNonLocalSourceRoutingEnableDisable OptionCode = 20 + OptionPolicyFilter OptionCode = 21 + OptionMaximumDatagramReassemblySize OptionCode = 22 + OptionDefaultIPTimeToLive OptionCode = 23 + OptionPathMTUAgingTimeout OptionCode = 24 + OptionPathMTUPlateauTable OptionCode = 25 + + // IP Layer Parameters per Interface + OptionInterfaceMTU OptionCode = 26 + OptionAllSubnetsAreLocal OptionCode = 27 + OptionBroadcastAddress OptionCode = 28 + OptionPerformMaskDiscovery OptionCode = 29 + OptionMaskSupplier OptionCode = 30 + OptionPerformRouterDiscovery OptionCode = 31 + OptionRouterSolicitationAddress OptionCode = 32 + OptionStaticRoute OptionCode = 33 + + // Link Layer Parameters per Interface + OptionTrailerEncapsulation OptionCode = 34 + OptionARPCacheTimeout OptionCode = 35 + OptionEthernetEncapsulation OptionCode = 36 + + // TCP Parameters + OptionTCPDefaultTTL OptionCode = 37 + OptionTCPKeepaliveInterval OptionCode = 38 + OptionTCPKeepaliveGarbage OptionCode = 39 + + // Application and Service Parameters + OptionNetworkInformationServiceDomain OptionCode = 40 + OptionNetworkInformationServers OptionCode = 41 + OptionNetworkTimeProtocolServers OptionCode = 42 + OptionVendorSpecificInformation OptionCode = 43 + OptionNetBIOSOverTCPIPNameServer OptionCode = 44 + OptionNetBIOSOverTCPIPDatagramDistributionServer OptionCode = 45 + OptionNetBIOSOverTCPIPNodeType OptionCode = 46 + OptionNetBIOSOverTCPIPScope OptionCode = 47 + OptionXWindowSystemFontServer OptionCode = 48 + OptionXWindowSystemDisplayManager OptionCode = 49 + OptionNetworkInformationServicePlusDomain OptionCode = 64 + OptionNetworkInformationServicePlusServers OptionCode = 65 + OptionMobileIPHomeAgent OptionCode = 68 + OptionSimpleMailTransportProtocol OptionCode = 69 + OptionPostOfficeProtocolServer OptionCode = 70 + OptionNetworkNewsTransportProtocol OptionCode = 71 + OptionDefaultWorldWideWebServer OptionCode = 72 + OptionDefaultFingerServer OptionCode = 73 + OptionDefaultInternetRelayChatServer OptionCode = 74 + OptionStreetTalkServer OptionCode = 75 + OptionStreetTalkDirectoryAssistance OptionCode = 76 + + OptionRelayAgentInformation OptionCode = 82 + + // DHCP Extensions + OptionRequestedIPAddress OptionCode = 50 + OptionIPAddressLeaseTime OptionCode = 51 + OptionOverload OptionCode = 52 + OptionDHCPMessageType OptionCode = 53 + OptionServerIdentifier OptionCode = 54 + OptionParameterRequestList OptionCode = 55 + OptionMessage OptionCode = 56 + OptionMaximumDHCPMessageSize OptionCode = 57 + OptionRenewalTimeValue OptionCode = 58 + OptionRebindingTimeValue OptionCode = 59 + OptionVendorClassIdentifier OptionCode = 60 + OptionClientIdentifier OptionCode = 61 + + OptionTFTPServerName OptionCode = 66 + OptionBootFileName OptionCode = 67 + + OptionUserClass OptionCode = 77 + + OptionClientArchitecture OptionCode = 93 + OptionClientNetworkInterfaceIdentifier OptionCode = 94 + OptionClientMachineIdentifier OptionCode = 97 + + OptionTZPOSIXString OptionCode = 100 + OptionTZDatabaseString OptionCode = 101 + + OptionDomainSearch OptionCode = 119 + + OptionClasslessRouteFormat OptionCode = 121 + + // From RFC3942 - Options Used by PXELINUX + OptionPxelinuxMagic OptionCode = 208 + OptionPxelinuxConfigfile OptionCode = 209 + OptionPxelinuxPathprefix OptionCode = 210 + OptionPxelinuxReboottime OptionCode = 211 +) + +/* Notes +A DHCP server always returns its own address in the 'server identifier' option. +DHCP defines a new 'client identifier' option that is used to pass an explicit client identifier to a DHCP server. +*/ diff --git a/pkg/util/dhcp/server.go b/pkg/util/dhcp/server.go new file mode 100644 index 0000000000..216de3b554 --- /dev/null +++ b/pkg/util/dhcp/server.go @@ -0,0 +1,87 @@ +package dhcp + +import ( + "fmt" + "net" + "runtime/debug" + + "yunion.io/x/log" +) + +type DHCPServer struct { + Address string + Port int + conn *Conn +} + +func NewDHCPServer(address string, port int) *DHCPServer { + return &DHCPServer{ + Address: address, + Port: port, + } +} + +func NewDHCPServer2(address string, port int) (*DHCPServer, *Conn, error) { + conn, err := NewSocketConn(fmt.Sprintf("%s:%d", address, port)) + if err != nil { + return nil, nil, err + } + return &DHCPServer{ + Address: address, + Port: port, + conn: conn, + }, conn, nil +} + +type DHCPHandler interface { + ServeDHCP(pkt Packet, addr *net.UDPAddr, intf *net.Interface) (Packet, error) +} + +func (s *DHCPServer) ListenAndServe(handler DHCPHandler) error { + if s.conn == nil { + dhcpAddr := fmt.Sprintf("%s:%d", s.Address, s.Port) + dhcpConn, err := NewConn(dhcpAddr) + if err != nil { + return fmt.Errorf("Listen DHCP connection error: %v", err) + } + s.conn = dhcpConn + } + defer s.conn.Close() + return s.serveDHCP(handler) +} + +func (s *DHCPServer) serveDHCP(handler DHCPHandler) error { + for { + pkt, addr, intf, err := s.conn.RecvDHCP() + if err != nil { + return fmt.Errorf("Receiving DHCP packet: %s", err) + } + // if intf == nil { + // return fmt.Errorf("Received DHCP packet with no interface information (this is a violation of dhcp4.Conn's contract)") + // } + + go func() { + defer func() { + if r := recover(); r != nil { + log.Errorf("Serve panic error: %v", r) + debug.PrintStack() + } + }() + + resp, err := handler.ServeDHCP(pkt, addr, intf) + if err != nil { + log.Warningf("[DHCP] handler serve error: %v", err) + return + } + if resp == nil { + // log.Warningf("[DHCP] hander response null packet") + return + } + //log.Debugf("[DHCP] send response packet: %s to interface: %#v", resp.DebugString(), intf) + if err = s.conn.SendDHCP(resp, addr, intf); err != nil { + log.Errorf("[DHCP] failed to response packet for %s: %v", pkt.CHAddr(), err) + return + } + }() + } +} diff --git a/pkg/util/esxi/fakeregion.go b/pkg/util/esxi/fakeregion.go index da0fe948cd..382741d433 100644 --- a/pkg/util/esxi/fakeregion.go +++ b/pkg/util/esxi/fakeregion.go @@ -1,6 +1,8 @@ package esxi import ( + "yunion.io/x/pkg/utils" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" ) @@ -67,3 +69,33 @@ func (cli *SESXiClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, func (cli *SESXiClient) GetProvider() string { return models.CLOUD_PROVIDER_VMWARE } + +func (cli *SESXiClient) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + storages, err := cli.GetIStorages() + if err != nil { + return nil, err + } + caches := make([]cloudprovider.ICloudStoragecache, 0) + cacheIds := make([]string, 0) + for i := range storages { + iCache := storages[i].GetIStoragecache() + if !utils.IsInStringArray(iCache.GetGlobalId(), cacheIds) { + caches = append(caches, iCache) + cacheIds = append(cacheIds, iCache.GetGlobalId()) + } + } + return caches, nil +} + +func (cli *SESXiClient) GetIStoragecacheById(idstr string) (cloudprovider.ICloudStoragecache, error) { + caches, err := cli.GetIStoragecaches() + if err != nil { + return nil, err + } + for i := range caches { + if caches[i].GetGlobalId() == idstr { + return caches[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/util/esxi/host.go b/pkg/util/esxi/host.go index f4f8781def..8cdd3bd18e 100644 --- a/pkg/util/esxi/host.go +++ b/pkg/util/esxi/host.go @@ -14,7 +14,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/util/billing" ) var HOST_SYSTEM_PROPS = []string{"name", "parent", "summary", "config", "hardware", "vm", "datastore"} @@ -77,6 +76,15 @@ func NewHost(manager *SESXiClient, host *mo.HostSystem, dc *SDatacenter) *SHost return &SHost{SManagedObject: newManagedObject(manager, host, dc)} } +func (self *SHost) GetName() string { + name := self.SManagedObject.GetName() + dotPos := strings.IndexByte(name, '.') + if dotPos > 0 { + name = name[:dotPos] + } + return name +} + func (self *SHost) GetMetadata() *jsonutils.JSONDict { return nil } @@ -498,13 +506,7 @@ func (self *SHost) GetVersion() string { return fmt.Sprintf("%s-%s", about.Version, about.Build) } -func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, vswitchId string, ipAddr string, desc string, - passwd string, storageType string, diskSizes []int, publicKey string, secGrpId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (self *SHost) CreateVM2(name string, imgId string, sysDiskSize int, instanceType string, vswitchId string, ipAddr string, desc string, - passwd string, storageType string, diskSizes []int, publicKey string, secGrpId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) { +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { return nil, cloudprovider.ErrNotImplemented } diff --git a/pkg/util/esxi/image.go b/pkg/util/esxi/image.go index 3788190d21..d54dbfb9a4 100644 --- a/pkg/util/esxi/image.go +++ b/pkg/util/esxi/image.go @@ -4,17 +4,21 @@ import ( "context" "path" "strings" + "time" "github.com/vmware/govmomi/object" "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" ) type SImage struct { cache *SDatastoreImageCache filename string + size int64 + createAt time.Time } func (self *SImage) getDatacenter() *object.Datacenter { @@ -34,7 +38,7 @@ func (self *SImage) GetId() string { } func (self *SImage) GetName() string { - return path.Base(self.filename) + return self.GetId() } func (self *SImage) GetGlobalId() string { @@ -46,9 +50,17 @@ func (self *SImage) GetStatus() string { ctx := context.Background() _, err := dm.QueryVirtualDiskInfo(ctx, self.getFullFilename(), self.getDatacenter(), true) if err != nil { - return "saving" + return models.CACHED_IMAGE_STATUS_CACHE_FAILED } - return "active" + return models.CACHED_IMAGE_STATUS_READY +} + +func (self *SImage) GetImageStatus() string { + status := self.GetStatus() + if status == models.CACHED_IMAGE_STATUS_READY { + return cloudprovider.IMAGE_STATUS_ACTIVE + } + return cloudprovider.IMAGE_STATUS_DELETED } func (self *SImage) Refresh() error { @@ -70,3 +82,39 @@ func (self *SImage) Delete(ctx context.Context) error { func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { return self.cache } + +func (self *SImage) GetImageType() string { + return cloudprovider.CachedImageTypeCustomized +} + +func (self *SImage) GetSize() int64 { + return self.size +} + +func (self *SImage) GetOsType() string { + return "" +} + +func (self *SImage) GetOsDist() string { + return "" +} + +func (self *SImage) GetOsVersion() string { + return "" +} + +func (self *SImage) GetOsArch() string { + return "" +} + +func (self *SImage) GetMinOsDiskSizeGb() int { + return int(self.GetSize() / 1024 / 1024 / 1024) +} + +func (self *SImage) GetImageFormat() string { + return "vmdk" +} + +func (self *SImage) GetCreateTime() time.Time { + return self.createAt +} diff --git a/pkg/util/esxi/provider/provider.go b/pkg/util/esxi/provider/provider.go index 82130232c1..da1a16d468 100644 --- a/pkg/util/esxi/provider/provider.go +++ b/pkg/util/esxi/provider/provider.go @@ -1,6 +1,7 @@ package provider import ( + "context" "fmt" "net/url" "strconv" @@ -10,6 +11,8 @@ import ( "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/esxi" ) @@ -24,6 +27,46 @@ func (self *SESXiProviderFactory) ValidateChangeBandwidth(instanceId string, ban return fmt.Errorf("Changing %s bandwidth is not supported", esxi.CLOUD_PROVIDER_VMWARE) } +func (self *SESXiProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) error { + username, _ := data.GetString("username") + if len(username) == 0 { + return httperrors.NewMissingParameterError("username") + } + password, _ := data.GetString("password") + if len(password) == 0 { + return httperrors.NewMissingParameterError("password") + } + host, _ := data.GetString("host") + if len(host) == 0 { + return httperrors.NewMissingParameterError("host") + } + port, _ := data.Int("port") + accessURL := fmt.Sprintf("https://%s:%d/sdk", host, port) + if port == 0 || port == 443 { + accessURL = fmt.Sprintf("https://%s/sdk", host) + } + data.Set("account", jsonutils.NewString(username)) + data.Set("secret", jsonutils.NewString(password)) + data.Set("access_url", jsonutils.NewString(accessURL)) + return nil +} + +func (self *SESXiProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, cloudaccount string) (*cloudprovider.SCloudaccount, error) { + username, _ := data.GetString("username") + if len(username) == 0 { + return nil, httperrors.NewMissingParameterError("username") + } + password, _ := data.GetString("password") + if len(password) == 0 { + return nil, httperrors.NewMissingParameterError("password") + } + account := &cloudprovider.SCloudaccount{ + Account: username, + Secret: password, + } + return account, nil +} + func parseHostPort(host string, defPort int) (string, int, error) { colonPos := strings.IndexByte(host, ':') if colonPos > 0 { @@ -111,3 +154,7 @@ func (self *SESXiProvider) GetBalance() (float64, error) { func (self *SESXiProvider) GetOnPremiseIRegion() (cloudprovider.ICloudRegion, error) { return self.client, nil } + +func (self *SESXiProvider) SupportPrepaidResources() bool { + return false +} diff --git a/pkg/util/esxi/storage.go b/pkg/util/esxi/storage.go index 7dcd53c2f9..2cca88c460 100644 --- a/pkg/util/esxi/storage.go +++ b/pkg/util/esxi/storage.go @@ -623,6 +623,14 @@ func (self *SDatastore) DeleteVmdk(ctx context.Context, remotePath string) error return nil } +func (self *SDatastore) GetVmdkInfo(ctx context.Context, remotePath string) (*vmdkutils.SVMDKInfo, error) { + vmdkContent, err := self.FileGetContent(ctx, remotePath) + if err != nil { + return nil, err + } + return vmdkutils.Parse(string(vmdkContent)) +} + func (self *SDatastore) CheckVmdk(ctx context.Context, remotePath string) error { dm := object.NewVirtualDiskManager(self.manager.client.Client) defer dm.Destroy(ctx) @@ -671,3 +679,7 @@ func (self *SDatastore) RemoveDir(ctx context.Context, remotePath string) error return dnm.DeleteDirectory(ctx, dcObj, remotePath) } + +func (self *SDatastore) IsSysDiskStore() bool { + return true +} diff --git a/pkg/util/esxi/storagecache.go b/pkg/util/esxi/storagecache.go index 89d85334ad..7cf042945a 100644 --- a/pkg/util/esxi/storagecache.go +++ b/pkg/util/esxi/storagecache.go @@ -79,9 +79,15 @@ func (self *SDatastoreImageCache) GetIImages() ([]cloudprovider.ICloudImage, err if err := self.datastore.CheckVmdk(ctx, filename); err != nil { continue } + vmdkInfo, err := self.datastore.GetVmdkInfo(ctx, filename) + if err != nil { + continue + } image := SImage{ cache: self, filename: filename, + size: vmdkInfo.Size(), + createAt: files[i].Date, } ret = append(ret, &image) vmdkName := files[i].Name diff --git a/pkg/util/fileutils2/fileutils.go b/pkg/util/fileutils2/fileutils.go new file mode 100644 index 0000000000..04bdd938af --- /dev/null +++ b/pkg/util/fileutils2/fileutils.go @@ -0,0 +1,641 @@ +package fileutils2 + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strconv" + "strings" + "syscall" + + "golang.org/x/sys/unix" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/regutils2" + "yunion.io/x/pkg/utils" +) + +func Cleandir(sPath string, keepdir bool) error { + if f, _ := os.Lstat(sPath); f == nil || f.Mode()&os.ModeSymlink == os.ModeSymlink { + return nil + } + files, _ := ioutil.ReadDir(sPath) + for _, file := range files { + fp := path.Join(sPath, file.Name()) + if f, _ := os.Lstat(fp); f.Mode()&os.ModeSymlink == os.ModeSymlink { + if !keepdir { + if err := os.Remove(fp); err != nil { + return err + } + } + } else if f.IsDir() { + Cleandir(fp, keepdir) + if !keepdir { + if err := os.Remove(fp); err != nil { + return err + } + } + } else { + if err := os.Remove(fp); err != nil { + return err + } + } + } + return nil +} + +// TODO: test +func Zerofiles(sPath string) error { + f, err := os.Lstat(sPath) + switch { + case err != nil: + return err + case f.Mode()&os.ModeSymlink == os.ModeSymlink: + // islink + return nil + case f.Mode().IsRegular(): + return FilePutContents(sPath, "", false) + case f.Mode().IsDir(): + files, err := ioutil.ReadDir(sPath) + if err != nil { + return err + } + for _, file := range files { + if file.Mode()&os.ModeSymlink == os.ModeSymlink { + continue + } else if file.Mode().IsRegular() { + if err := FilePutContents(path.Join(sPath, file.Name()), "", false); err != nil { + return err + } + } else if file.Mode().IsDir() { + return Zerofiles(path.Join(sPath, file.Name())) + } + } + } + return nil +} + +func FilePutContents(filename string, content string, modAppend bool) error { + var mode = os.O_WRONLY | os.O_CREATE + if modAppend { + mode = mode | os.O_APPEND + } else { + mode = mode | os.O_TRUNC + } + fd, err := os.OpenFile(filename, mode, 0644) + if err != nil { + return err + } + defer fd.Close() + _, err = fd.WriteString(content) + return err +} + +func IsBlockDevMounted(dev string) bool { + devPath := "/dev/" + dev + mounts, err := procutils.NewCommand("mount").Run() + if err != nil { + return false + } + for _, s := range strings.Split(string(mounts), "\n") { + if strings.HasPrefix(s, devPath) { + return true + } + } + return false +} + +func IsBlockDeviceUsed(dev string) bool { + if strings.HasPrefix(dev, "/dev/") { + dev = dev[strings.LastIndex(dev, "/")+1:] + } + devStr := fmt.Sprintf(" %s\n", dev) + devs, _ := procutils.NewCommand("cat", "/proc/partitions").Run() + if idx := strings.Index(string(devs), devStr); idx > 0 { + return true + } + return false +} + +func ChangeAllBlkdevsParams(params map[string]string) { + if _, err := os.Stat("/sys/block"); !os.IsNotExist(err) { + blockDevs, err := ioutil.ReadDir("/sys/block") + if err != nil { + log.Errorln(err) + return + } + for _, b := range blockDevs { + if IsBlockDevMounted(b.Name()) { + for k, v := range params { + ChangeBlkdevParameter(b.Name(), k, v) + } + } + } + } +} + +func ChangeBlkdevParameter(dev, key, value string) { + p := path.Join("/sys/block", dev, key) + if _, err := os.Stat(p); !os.IsNotExist(err) { + err = FilePutContents(p, value, false) + if err != nil { + log.Errorf("Fail to set %s of %s to %s:%s", key, dev, value, err) + } + log.Infof("Set %s of %s to %s", key, dev, value) + } +} + +func FileGetContents(file string) (string, error) { + content, err := ioutil.ReadFile(file) + if err != nil { + return "", err + } + return string(content), nil +} + +func GetFsFormat(diskPath string) string { + ret, err := procutils.NewCommand("blkid", "-o", "value", "-s", "TYPE", diskPath).Run() + if err != nil { + return "" + } + var res string + for _, line := range strings.Split(string(ret), "\n") { + res += line + } + return res +} + +func CleanFailedMountpoints() { + var mtfile = "/etc/mtab" + if _, err := os.Stat(mtfile); os.IsNotExist(err) { + mtfile = "/proc/mounts" + } + f, err := os.Open(mtfile) + if err != nil { + log.Errorf("CleanFailedMountpoints error: %s", err) + } + reader := bufio.NewReader(f) + line, _, err := reader.ReadLine() + for err != nil { + m := strings.Split(string(line), " ") + if len(m) > 1 { + mp := m[1] + if _, err := os.Stat(mp); os.IsNotExist(err) { + log.Warningf("Mount point %s not exists", mp) + } + procutils.NewCommand("umount", mp).Run() + } + } +} + +type HostsFile map[string][]string + +func (hf HostsFile) Parse(content string) { + lines := strings.Split(content, "\n") + for _, line := range lines { + data := regexp.MustCompile(`\s+`).Split(line, -1) + for len(data) > 0 && data[len(data)-1] == "" { + data = data[:len(data)-1] + } + if len(data) > 1 { + hf[data[0]] = data[1:] + } + } +} + +func (hf HostsFile) Add(name string, value ...string) { + hf[name] = value +} + +func (hf HostsFile) String() string { + var ret = "" + for k, v := range hf { + if len(v) > 0 { + ret += fmt.Sprintf("%s\t%s\n", k, strings.Join(v, "\t")) + } + } + return ret +} + +func Writable(path string) bool { + return unix.Access(path, unix.W_OK) == nil +} + +func FsFormatToDiskType(fsFormat string) string { + switch { + case fsFormat == "swap": + return "linux-swap" + case strings.HasPrefix(fsFormat, "ext") || fsFormat == "xfs": + return "ext2" + case strings.HasPrefix(fsFormat, "fat"): + return "fat32" + case fsFormat == "ntfs": + return fsFormat + default: + return "" + } +} + +func Mkpartition(imagePath, fsFormat string) error { + var ( + parted = "/sbin/parted" + labelType = "gpt" + diskType = FsFormatToDiskType(fsFormat) + ) + + if len(diskType) == 0 { + return fmt.Errorf("Unknown fsFormat %s", fsFormat) + } + + // 创建一个新磁盘分区表类型, ex: mbr gpt msdos ... + _, err := procutils.NewCommand(parted, "-s", imagePath, "mklabel", labelType).Run() + if err != nil { + log.Errorf("mklabel %s %s error %s", imagePath, fsFormat, err) + return err + } + + // 创建一个part-type类型的分区, part-type可以是:"primary", "logical", "extended" + // 如果指定fs-type(即diskType)则在创建分区的同时进行格式化 + _, err = procutils.NewCommand(parted, "-s", "-a", "cylinder", + imagePath, "mkpart", "primary", diskType, "0", "100%").Run() + if err != nil { + log.Errorf("mkpart %s %s error %s", imagePath, fsFormat, err) + return err + } + return nil +} + +func FormatPartition(path, fs, uuid string) error { + var cmd, cmdUuid []string + switch { + case fs == "swap": + cmd = []string{"mkswap", "-U", uuid} + case fs == "ext2": + cmd = []string{"mkfs.ext2"} + cmdUuid = []string{"tune2fs", "-U", uuid} + case fs == "ext3": + cmd = []string{"mkfs.ext3"} + cmdUuid = []string{"tune2fs", "-U", uuid} + case fs == "ext4": + cmd = []string{"mkfs.ext4", "-O", "^64bit", "-E", "lazy_itable_init=1"} + cmdUuid = []string{"tune2fs", "-U", uuid} + case fs == "ext4dev": + cmd = []string{"mkfs.ext4dev", "-E", "lazy_itable_init=1"} + cmdUuid = []string{"tune2fs", "-U", uuid} + case strings.HasPrefix(fs, "fat"): + cmd = []string{"mkfs.msdos"} + // #case fs == "ntfs": + // # cmd = []string{"/sbin/mkfs.ntfs"} + case fs == "xfs": + cmd = []string{"/sbin/mkfs.xfs", "-f", "-m", "crc=0", "-i", "projid32bit=0", "-n", "ftype=0"} + cmdUuid = []string{"xfs_admin", "-U", uuid} + } + + if len(cmd) > 0 { + var cmds = cmd + cmds = append(cmds, path) + if _, err := procutils.NewCommand(cmds[0], cmds[1:]...).Run(); err != nil { + log.Errorln(err) + return err + } + if len(cmdUuid) > 0 { + cmds = cmdUuid + cmds = append(cmds, path) + if _, err := procutils.NewCommand(cmds[0], cmds[1:]...).Run(); err != nil { + log.Errorln(err) + return err + } + } + return nil + } + return fmt.Errorf("Unknown fs %s", fs) +} + +func IsPartedFsString(fsstr string) bool { + return utils.IsInStringArray(strings.ToLower(fsstr), []string{ + "ext2", "ext3", "ext4", "xfs", + "fat16", "fat32", + "hfs", "hfs+", "hfsx", + "linux-swap", "linux-swap(v1)", + "ntfs", "reiserfs", "ufs", "btrfs", + }) +} + +func ParseDiskPartition(dev string, lines []byte) ([][]string, string) { + var ( + parts = [][]string{} + label string + labelPartten = regexp.MustCompile(`Partition Table:\s+(?P