diff --git a/build/docker/Dockerfile.esxi-agent b/build/docker/Dockerfile.esxi-agent new file mode 100644 index 0000000000..38bac16e5f --- /dev/null +++ b/build/docker/Dockerfile.esxi-agent @@ -0,0 +1,4 @@ +FROM registry.cn-beijing.aliyuncs.com/yunionio/onecloud-base:latest + +COPY ./build/esxi-agent/root/opt/ /opt/ +ADD ./_output/bin/esxi-agent /opt/yunion/bin/esxi-agent diff --git a/build/esxi-agent/root/opt/yunion/share/vmware/ovf.xml b/build/esxi-agent/root/opt/yunion/share/vmware/ovf.xml new file mode 100644 index 0000000000..a62536bfe8 --- /dev/null +++ b/build/esxi-agent/root/opt/yunion/share/vmware/ovf.xml @@ -0,0 +1,65 @@ + + + + + + + Virtual disk information + + + + A virtual machine + {{ .ImportName }} + + The kind of installed guest operating system + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + {{ .ImportName }} + vmx-07 + + + hertz * 10^6 + Number of Virtual CPUs + 1 virtual CPU(s) + 1 + 3 + 1 + + + byte * 2^20 + Memory Size + 1024MB of memory + 2 + 4 + 1024 + + + 0 + SCSI Controller + SCSI Controller 0 + 3 + VirtualSCSI + 6 + + + 0 + Hard Disk 1 + ovf:/disk/vmdisk1 + 9 + 3 + 17 + + + + + diff --git a/build/esxi-agent/vars b/build/esxi-agent/vars new file mode 100644 index 0000000000..3aedc96281 --- /dev/null +++ b/build/esxi-agent/vars @@ -0,0 +1,2 @@ +DESCRIPTION="Esxi Agent" +# SERVICE="yes" diff --git a/cmd/esxi-agent/main.go b/cmd/esxi-agent/main.go new file mode 100644 index 0000000000..2cdf1e65ec --- /dev/null +++ b/cmd/esxi-agent/main.go @@ -0,0 +1,26 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "yunion.io/x/onecloud/pkg/esxi/service" + "yunion.io/x/onecloud/pkg/util/atexit" +) + +func main() { + defer atexit.Handle() + + service.New().StartService() +} diff --git a/pkg/cloudcommon/agent/agent.go b/pkg/cloudcommon/agent/agent.go index a208cbb62c..19047305c1 100644 --- a/pkg/cloudcommon/agent/agent.go +++ b/pkg/cloudcommon/agent/agent.go @@ -24,6 +24,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/version" + "yunion.io/x/onecloud/pkg/cloudcommon/agent/iagent" "yunion.io/x/onecloud/pkg/cloudcommon/object" "yunion.io/x/onecloud/pkg/hostman/storageman" "yunion.io/x/onecloud/pkg/mcclient" @@ -31,19 +32,6 @@ import ( "yunion.io/x/onecloud/pkg/util/httputils" ) -type IAgent interface { - GetAgentType() string - GetAccessIP() (net.IP, error) - GetListenIP() (net.IP, error) - GetPort() int - GetEnableSsl() bool - GetZoneName() string - GetAdminSession() *mcclient.ClientSession - TuneSystem() error - StartService() error - StopService() error -} - type SZoneInfo struct { Name string `json:"name"` Id string `json:"id"` @@ -80,11 +68,11 @@ func getIfaceIPs(iface *net.Interface) ([]net.IP, error) { return ips, nil } -func (agent *SBaseAgent) IAgent() IAgent { - return agent.GetVirtualObject().(IAgent) +func (agent *SBaseAgent) IAgent() iagent.IAgent { + return agent.GetVirtualObject().(iagent.IAgent) } -func (agent *SBaseAgent) Init(iagent IAgent, ifname string, cachePath string) error { +func (agent *SBaseAgent) Init(iagent iagent.IAgent, ifname string, cachePath string) error { iface, err := net.InterfaceByName(ifname) if err != nil { return err diff --git a/pkg/cloudcommon/agent/iagent/doc.go b/pkg/cloudcommon/agent/iagent/doc.go new file mode 100644 index 0000000000..586cf249d4 --- /dev/null +++ b/pkg/cloudcommon/agent/iagent/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iagent // import "yunion.io/x/onecloud/pkg/cloudcommon/agent/iagent" diff --git a/pkg/cloudcommon/agent/iagent/interface.go b/pkg/cloudcommon/agent/iagent/interface.go new file mode 100644 index 0000000000..47e66c22ee --- /dev/null +++ b/pkg/cloudcommon/agent/iagent/interface.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iagent + +import ( + "net" + + "yunion.io/x/onecloud/pkg/mcclient" +) + +type IAgent interface { + GetAgentType() string + GetAccessIP() (net.IP, error) + GetListenIP() (net.IP, error) + GetPort() int + GetEnableSsl() bool + GetZoneName() string + GetAdminSession() *mcclient.ClientSession + TuneSystem() error + StartService() error + StopService() error +} diff --git a/pkg/cloudcommon/workmanager/manager.go b/pkg/cloudcommon/workmanager/manager.go index 7f90d65c7c..1863fc3584 100644 --- a/pkg/cloudcommon/workmanager/manager.go +++ b/pkg/cloudcommon/workmanager/manager.go @@ -65,6 +65,8 @@ func (w *SWorkManager) delayTask(ctx context.Context, task DelayTaskFunc, params w.delayTaskWithoutReqctx(ctx, task, params, worker) return } else { + // delayTask should have a new context.Context with value 'taskid' + ctx = context.WithValue(context.Background(), appctx.APP_CONTEXT_KEY_TASK_ID, ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID)) w.add() worker.Run(func() { defer w.done() @@ -109,6 +111,8 @@ func (w *SWorkManager) DelayTaskWithoutReqctx(ctx context.Context, task DelayTas func (w *SWorkManager) delayTaskWithoutReqctx( ctx context.Context, task DelayTaskFunc, params interface{}, worker *appsrv.SWorkerManager, ) { + // delayTaskWithReqctx should have a new context.Context + ctx = context.Background() w.add() w.worker.Run(func() { defer w.done() diff --git a/pkg/esxi/agent.go b/pkg/esxi/agent.go new file mode 100644 index 0000000000..06cccc21c6 --- /dev/null +++ b/pkg/esxi/agent.go @@ -0,0 +1,175 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package esxi + +import ( + "context" + "fmt" + "net" + "net/http" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon/agent" + "yunion.io/x/onecloud/pkg/cloudcommon/workmanager" + "yunion.io/x/onecloud/pkg/esxi/options" + "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" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/netutils2" +) + +const ( + _ = compute.AgentTypeEsxi +) + +var ( + EsxiAgent *SEsxiAgent +) + +type SEsxiAgent struct { + agent.SBaseAgent + + agentImageCache *storageman.SAgentImageCacheManager + AgentStorage *storageman.SAgentStorage + ListenNic netutils2.SNetInterface +} + +func NewEsxiAgent() (*SEsxiAgent, error) { + agent := &SEsxiAgent{} + err := agent.Init(agent, options.Options.ListenInterface, options.Options.ImageCachePath) + if err != nil { + return nil, err + } + return agent, nil +} + +func (ea *SEsxiAgent) GetAgentType() string { + return string(compute.AgentTypeEsxi) +} + +func (ea *SEsxiAgent) GetAccessIP() (net.IP, error) { + return ea.GetListenIP() +} + +func (ea *SEsxiAgent) GetListenIP() (net.IP, error) { + return ea.FindListenIP(options.Options.ListenAddress) +} + +func (ea *SEsxiAgent) GetPort() int { + return options.Options.Port +} + +func (ea *SEsxiAgent) GetEnableSsl() bool { + return options.Options.EnableSsl +} + +func (ea *SEsxiAgent) GetZoneName() string { + return options.Options.Zone +} + +func (ea *SEsxiAgent) GetAdminSession() *mcclient.ClientSession { + return auth.GetAdminSession(context.TODO(), options.Options.Region, "v2") +} + +func (ea *SEsxiAgent) TuneSystem() error { + return nil +} + +func (ea *SEsxiAgent) StartService() error { + + ea.DoOnline(ea.GetAdminSession()) + return nil +} + +func (ea *SEsxiAgent) StopService() error { + return nil +} + +func (ea *SEsxiAgent) Start() error { + err := ea.SBaseAgent.Start() + if err != nil { + return err + } + // add agent image cache + ea.agentImageCache = storageman.NewAgentImageCacheManager(ea.CacheManager) + ea.AgentStorage = storageman.NewAgentStorage(&storageman.SStorageManager{LocalStorageImagecacheManager: ea.CacheManager}, + ea, options.Options.AgentTempPath) + return nil +} + +func Start(app *appsrv.Application) error { + var err error + if EsxiAgent != nil { + log.Warningf("Global EsxiAgent already start") + return nil + } + EsxiAgent, err = NewEsxiAgent() + if err != nil { + return err + } + err = EsxiAgent.Start() + if err != nil { + return err + } + EsxiAgent.AddImageCacheHandler("", app) + return nil +} + +func (agent *SEsxiAgent) AddImageCacheHandler(prefix string, app *appsrv.Application) { + hostutils.InitWorkerManager() + app.AddHandler("POST", + fmt.Sprintf("%s/disks/image_cache", prefix), + auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + performImageCache(ctx, w, r, agent.agentImageCache.PrefetchImageCache) + })) + app.AddHandler("DELETE", + fmt.Sprintf("%s/disks/image_cache", prefix), + auth.Authenticate(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + performImageCache(ctx, w, r, agent.agentImageCache.DeleteImageCache) + })) +} + +func performImageCache( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + performTask workmanager.DelayTaskFunc, +) { + _, _, body := appsrv.FetchEnv(ctx, w, r) + + disk, err := body.Get("disk") + if err != nil { + httperrors.MissingParameterError(w, "disk") + return + } + + hostutils.DelayTask(ctx, performTask, disk) + hostutils.ResponseOk(ctx, w) +} + +func Stop() error { + if EsxiAgent != nil { + log.Infof("EsxiAgent stop...") + tmpAgent := EsxiAgent + EsxiAgent = nil + tmpAgent.Stop() + } + return nil +} diff --git a/pkg/esxi/doc.go b/pkg/esxi/doc.go new file mode 100644 index 0000000000..683f7a5876 --- /dev/null +++ b/pkg/esxi/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package esxi // import "yunion.io/x/onecloud/pkg/esxi" diff --git a/pkg/esxi/handler/doc.go b/pkg/esxi/handler/doc.go new file mode 100644 index 0000000000..7b1bdec7a8 --- /dev/null +++ b/pkg/esxi/handler/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler // import "yunion.io/x/onecloud/pkg/esxi/handler" diff --git a/pkg/esxi/handler/handlers.go b/pkg/esxi/handler/handlers.go new file mode 100644 index 0000000000..1fb955ac17 --- /dev/null +++ b/pkg/esxi/handler/handlers.go @@ -0,0 +1,181 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +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/esxi" + "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" +) + +const ( + AGENT_PREFIX = "disks/agent" +) + +func InitHandlers(app *appsrv.Application) { + initESXIHandler(app) +} + +var defaultHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + httperrors.NotImplementedError(w, "") + return +} + +func IdAgentPrefix(action string) string { + return fmt.Sprintf("%s/%s/", AGENT_PREFIX, action) +} + +func AgentPrefix(action string) string { + return fmt.Sprintf("%s/%s", AGENT_PREFIX, action) +} + +func initESXIHandler(app *appsrv.Application) { + + app.AddHandler("POST", AgentPrefix("upload"), auth.Authenticate(uploadHandler)) + app.AddHandler("POST", AgentPrefix("deploy"), auth.Authenticate(deployHandler)) + app.AddHandler("POST", IdAgentPrefix("delete"), auth.Authenticate(deleteHandler)) + app.AddHandler("POST", IdAgentPrefix("create"), auth.Authenticate(createHandler)) + app.AddHandler("POST", IdAgentPrefix("save-prepare"), auth.Authenticate(savePrepareHandler)) + app.AddHandler("POST", IdAgentPrefix("resize"), auth.Authenticate(resizeHandler)) + app.AddHandler("POST", IdAgentPrefix("clone"), auth.Authenticate(defaultHandler)) + app.AddHandler("POST", IdAgentPrefix("fetch"), auth.Authenticate(defaultHandler)) + app.AddHandler("POST", IdAgentPrefix("post-migrate"), auth.Authenticate(defaultHandler)) + app.AddHandler("POST", IdAgentPrefix("snapshot"), auth.Authenticate(defaultHandler)) + app.AddHandler("POST", IdAgentPrefix("reset"), auth.Authenticate(defaultHandler)) + app.AddHandler("POST", IdAgentPrefix("cleanup-snapshots"), auth.Authenticate(defaultHandler)) +} + +func uploadHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + _, _, body := appsrv.FetchEnv(ctx, w, r) + disk, err := body.Get("disk") + if err != nil { + httperrors.MissingParameterError(w, "miss disk") + return + } + hostutils.DelayTask(ctx, esxi.EsxiAgent.AgentStorage.SaveToGlance, disk) + hostutils.ResponseOk(ctx, w) +} + +func deployHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + log.Debugf("enter deployHandler") + _, _, body := appsrv.FetchEnv(ctx, w, r) + disk, err := body.Get("disk") + if err != nil { + httperrors.MissingParameterError(w, "miss disk") + return + } + hostutils.DelayTask(ctx, esxi.EsxiAgent.AgentStorage.AgentDeployGuest, disk) + hostutils.ResponseOk(ctx, w) + +} + +func deleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, _ := appsrv.FetchEnv(ctx, w, r) + diskId := params[""] + disk := esxi.EsxiAgent.AgentStorage.GetDiskById(diskId) + if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId == nil { + if disk != nil { + _, err := disk.Delete(ctx, nil) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + hostutils.ResponseOk(ctx, w) + return + } + } + hostutils.ResponseOk(ctx, w) + hostutils.DelayTask(ctx, disk.Delete, nil) +} + +func createHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + disk, diskInfo, err := diskAndDiskInfo(ctx, w, r) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + hostutils.DelayTask(ctx, esxi.EsxiAgent.AgentStorage.CreateDiskByDiskInfo, + storageman.SDiskCreateByDiskinfo{DiskId: disk.GetId(), Disk: disk, DiskInfo: diskInfo}) + hostutils.ResponseOk(ctx, w) +} + +func savePrepareHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID).(string) + disk, diskInfo, err := diskAndDiskInfo(ctx, w, r) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + hostutils.DelayTask(ctx, disk.PrepareSaveToGlance, storageman.PrepareSaveToGlanceParams{taskId, diskInfo}) + hostutils.ResponseOk(ctx, w) +} + +func resizeHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + disk, diskInfo, err := diskAndDiskInfo(ctx, w, r) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + hostutils.DelayTask(ctx, disk.Resize, diskInfo) + hostutils.ResponseOk(ctx, w) +} + +/* +func fetchHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + diskId := params[""] + disk := esxi.EsxiAgent.AgentStorage.GetDiskById(diskId) + if disk != nil { + httperrors.GeneralServerError(w, httperrors.NewDuplicateResourceError("disk '%s'", diskId)) + return + } + disk := esxi.EsxiAgent.AgentStorage.CreateDisk(diskId) + diskInfo, err := body.Get("disk") + if err != nil { + httperrors.InputParameterError(w, "miss disk") + } + url, err := diskInfo.GetString("url") + if err != nil { + httperrors.InputParameterError(w, "miss disk.url") + } + hostutils.DelayTask(ctx, disk.CreateFromUrl) + hostutils.ResponseOk(ctx, w) +} +*/ + +func diskAndDiskInfo(ctx context.Context, w http.ResponseWriter, r *http.Request) (storageman.IDisk, jsonutils.JSONObject, error) { + params, _, body := appsrv.FetchEnv(ctx, w, r) + diskId := params[""] + disk := esxi.EsxiAgent.AgentStorage.GetDiskById(diskId) + if disk == nil { + return nil, nil, httperrors.NewNotFoundError("disk '%s'", diskId) + } + diskInfo, err := body.Get("disk") + if err != nil { + return nil, nil, httperrors.NewMissingParameterError("miss disk") + } + return disk, diskInfo, nil +} diff --git a/pkg/esxi/options/doc.go b/pkg/esxi/options/doc.go new file mode 100644 index 0000000000..63c1c9c607 --- /dev/null +++ b/pkg/esxi/options/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package options // import "yunion.io/x/onecloud/pkg/esxi/options" diff --git a/pkg/esxi/options/options.go b/pkg/esxi/options/options.go new file mode 100644 index 0000000000..1f52f27b31 --- /dev/null +++ b/pkg/esxi/options/options.go @@ -0,0 +1,38 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package options + +import common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" + +type EsxiOptions struct { + common_options.CommonOptions + + ListenInterface string `help:"Master address of host server" default:"br0"` + ListenAddress string `help:"Host serve IP address to select when multiple address bind to ListenInterface"` + EsxiAgentPath string `default:"/opt/cloud/workspace/esxi_agent" help:"Path for esxi agent 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:"20"` + LinuxDefaultRootUser bool `help:"Default account for Linux system is root" default:"false"` + WindowsDefaultAdminUser bool `help:"Default account for Windows system is Administrator" default:"true"` + DefaultImageSaveFormat string `help:"Default image save format, default is vmdk, canbe qcow2" default:"vmdk"` + Zone string `help:"Zone where the agent locates"` + DeployServerSocketPath string `help:"Deploy server listen socket path" default:"/var/run/deploy.sock"` +} + +var ( + Options EsxiOptions +) diff --git a/pkg/esxi/service/doc.go b/pkg/esxi/service/doc.go new file mode 100644 index 0000000000..a13c4606fe --- /dev/null +++ b/pkg/esxi/service/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service // import "yunion.io/x/onecloud/pkg/esxi/service" diff --git a/pkg/esxi/service/esxi_agent_service.go b/pkg/esxi/service/esxi_agent_service.go new file mode 100644 index 0000000000..46d9dc125b --- /dev/null +++ b/pkg/esxi/service/esxi_agent_service.go @@ -0,0 +1,94 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "os" + "path/filepath" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + app_common "yunion.io/x/onecloud/pkg/cloudcommon/app" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + options_common "yunion.io/x/onecloud/pkg/cloudcommon/options" + "yunion.io/x/onecloud/pkg/cloudcommon/service" + "yunion.io/x/onecloud/pkg/esxi" + "yunion.io/x/onecloud/pkg/esxi/handler" + "yunion.io/x/onecloud/pkg/esxi/options" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient" +) + +type SExsiAgentService struct { + service.SServiceBase +} + +func New() *SExsiAgentService { + return &SExsiAgentService{} +} + +func (s *SExsiAgentService) StartService() { + options_common.ParseOptions(&options.Options, os.Args, "esxiagent.conf", "esxiagent") + + if len(options.Options.ImageCachePath) == 0 { + options.Options.ImageCachePath = filepath.Join(filepath.Dir(options.Options.EsxiAgentPath), "image_cache") + log.Infof("No cachepath, use default %s", options.Options.ImageCachePath) + err := os.MkdirAll(options.Options.ImageCachePath, 0760) + if err != nil { + log.Fatalf("fail to create ImageCachePath %s", options.Options.ImageCachePath) + } + } + if len(options.Options.AgentTempPath) == 0 { + options.Options.AgentTempPath = filepath.Join(filepath.Dir(options.Options.EsxiAgentPath), "agent_tmp") + log.Infof("No agent temp path, use default %s", options.Options.AgentTempPath) + err := os.MkdirAll(options.Options.AgentTempPath, 0760) + if err != nil { + log.Fatalf("fail to create AgentTempPath %s", options.Options.AgentTempPath) + } + } + + // init lockman + s.InitLockman() + + app_common.InitAuth(&options.Options.CommonOptions, func() { + log.Infof("auth complete") + }) + + fsdriver.Init(nil) + deployclient.Init(options.Options.DeployServerSocketPath) + + app := app_common.InitApp(&options.Options.BaseOptions, false) + handler.InitHandlers(app) + + s.startAgent(app) + + app_common.ServeForeverWithCleanup(app, &options.Options.BaseOptions, func() { + esxi.Stop() + }) +} + +func (s *SExsiAgentService) startAgent(app *appsrv.Application) { + err := esxi.Start(app) + if err != nil { + log.Fatalf("Start agent error: %v", err) + } +} + +func (s *SExsiAgentService) InitLockman() { + log.Infof("using inmemory lockman") + lm := lockman.NewInMemoryLockManager() + lockman.Init(lm) +} diff --git a/pkg/hostman/diskutils/disk.go b/pkg/hostman/diskutils/disk.go new file mode 100644 index 0000000000..e755dccbef --- /dev/null +++ b/pkg/hostman/diskutils/disk.go @@ -0,0 +1,26 @@ +package diskutils + +import ( + comapi "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" +) + +type IDisk interface { + Connect() bool + Disconnect() bool + MountRootfs() fsdriver.IRootFsDriver + UmountRootfs(driver fsdriver.IRootFsDriver) +} + +func GetIDisk(params *apis.DeployParams) IDisk { + hypervisor := params.GuestDesc.Hypervisor + switch hypervisor { + case comapi.HYPERVISOR_KVM: + return NewKVMGuestDisk(params.DiskPath) + case comapi.HYPERVISOR_ESXI: + return NewVDDKDisk(params.VddkInfo, params.DiskPath) + default: + return NewKVMGuestDisk(params.DiskPath) + } +} diff --git a/pkg/hostman/diskutils/diskutils.go b/pkg/hostman/diskutils/diskutils.go index 951e17b195..401aa4c532 100644 --- a/pkg/hostman/diskutils/diskutils.go +++ b/pkg/hostman/diskutils/diskutils.go @@ -308,6 +308,10 @@ func (d *SKVMGuestDisk) DetectIsUEFISupport(rootfs fsdriver.IRootFsDriver) bool return false } +func (d *SKVMGuestDisk) MountRootfs() fsdriver.IRootFsDriver { + return d.MountKvmRootfs() +} + func (d *SKVMGuestDisk) MountKvmRootfs() fsdriver.IRootFsDriver { return d.mountKvmRootfs(false) } @@ -340,6 +344,10 @@ func (d *SKVMGuestDisk) UmountKvmRootfs(fd fsdriver.IRootFsDriver) { } } +func (d *SKVMGuestDisk) UmountRootfs(fd fsdriver.IRootFsDriver) { + d.UmountKvmRootfs(fd) +} + func (d *SKVMGuestDisk) MakePartition(fs string) error { return Mkpartition(d.nbdDev, fs) } diff --git a/pkg/hostman/diskutils/vddk.go b/pkg/hostman/diskutils/vddk.go new file mode 100644 index 0000000000..a8deb1d57f --- /dev/null +++ b/pkg/hostman/diskutils/vddk.go @@ -0,0 +1,381 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskutils + +import ( + "bytes" + "crypto/sha1" + "crypto/tls" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" + "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" +) + +const ( + TMPDIR = "/tmp/vmware-root" +) + +var ( + MNT_PATTERN = regexp.MustCompile(`Disk flat file mounted under ([^\s]+)`) +) + +type VDDKDisk struct { + Host string + Port int + User string + Passwd string + VmRef string + DiskPath string + + FUseDir string + PartDirs []string + Proc *Command + Pid int +} + +func NewVDDKDisk(vddkInfo *apis.VDDKConInfo, diskPath string) *VDDKDisk { + return &VDDKDisk{ + Host: vddkInfo.Host, + Port: int(vddkInfo.Port), + User: vddkInfo.User, + Passwd: vddkInfo.Passwd, + VmRef: vddkInfo.Vmref, + DiskPath: diskPath, + } +} + +type Command struct { + *exec.Cmd + done chan error + stdouterr *bytes.Buffer + stdin io.Writer +} + +func NewCommand(name string, arg ...string) *Command { + cmd := Command{ + Cmd: exec.Command(name, arg...), + done: make(chan error, 1), + } + cmd.stdouterr = bytes.NewBuffer([]byte{}) + cmd.Stdout = cmd.stdouterr + cmd.Stderr = cmd.stdouterr + cmd.stdin, _ = cmd.StdinPipe() + return &cmd +} + +func (c *Command) Send(msg []byte) error { + _, err := c.stdin.Write(msg) + return err +} + +func (c *Command) Start() error { + if err := c.Cmd.Start(); err != nil { + return err + } + go func() { + c.done <- c.Cmd.Wait() + }() + return nil +} + +func (c *Command) Exited() bool { + return len(c.done) == 1 +} + +// Wait will block +func (c *Command) Wait() error { + return <-c.done +} + +func (c *Command) Kill() { + c.Process.Kill() +} + +func execpath() string { + return "/opt/vmware-vddk/bin/vix-mntapi-sample" +} + +func libdir() string { + return "/usr/lib/vmware" +} + +func logpath(pid int) string { + return fmt.Sprintf("%s/vixDiskLib-%d.log", TMPDIR, pid) +} + +func (vd *VDDKDisk) Connect() bool { + return true +} + +func (vd *VDDKDisk) Disconnect() bool { + return true +} + +func (vd *VDDKDisk) MountRootfs() fsdriver.IRootFsDriver { + if err := vd.Mount(); err == nil { + for _, mntPath := range vd.PartDirs { + part := newVDDKPartition(mntPath) + if fs := guestfs.DetectRootFs(part); fs != nil { + log.Infof("Use rootfs %s", fs) + return fs + } + } + } + return nil +} + +func (vd *VDDKDisk) UmountRootfs(fd fsdriver.IRootFsDriver) { + err := vd.Umount() + if err != nil { + log.Errorf("VDDKDisk Umount failed: %s", err) + } +} + +func (vd *VDDKDisk) ParsePartitions(buf string) error { + // Disk flat file mounted under /run/vmware/fuse/7673253059900458465 + // Mounted Volume 1, Type 1, isMounted 1, symLink /tmp/vmware-root/7673253059900458465_1, numGuestMountPoints 0 () + // print buf + ms := MNT_PATTERN.FindAllStringSubmatch(buf, -1) + if len(ms) != 0 { + vd.FUseDir = ms[0][1] + diskId := filepath.Base(vd.FUseDir) + files, err := ioutil.ReadDir(TMPDIR) + if err != nil { + return errors.Wrapf(err, "ioutil.ReadDir for %s", TMPDIR) + } + for _, f := range files { + if strings.HasPrefix(f.Name(), diskId) { + vd.PartDirs = append(vd.PartDirs, filepath.Join(TMPDIR, f.Name())) + } + } + } + log.Infof("Fuse path: %s partitiaons: %s", vd.FUseDir, vd.PartDirs) + return nil +} + +func (vd *VDDKDisk) Mount() (err error) { + defer func() { + if err == nil { + return + } + vd.Proc = nil + log.Errorf("Exec vix-mntapi-sample error: %s", err) + }() + + err = vd.ExecProg() + if err != nil { + return errors.Wrap(err, "VDDKDisk.ExecProg") + } + err = vd.WaitMounted() + if err != nil { + return errors.Wrap(err, "VDDKDisk.Mount") + } + return nil +} + +func (vd *VDDKDisk) Umount() error { + if vd.Proc != nil { + err := vd.Proc.Send([]byte{'y'}) + if err != nil { + errors.Wrap(err, "send 'y' to VDDKDisk.Proc") + } + err = vd.Proc.Wait() + if err != nil { + return errors.Wrap(err, "vd.Proc.Wait") + } + } + if len(vd.FUseDir) != 0 { + for _, p := range append(vd.PartDirs, vd.FUseDir) { + vd.fuseUmount(p) + } + } + if vd.Pid != 0 { + logpath := logpath(vd.Pid) + _, err := os.Stat(logpath) + if err == nil || os.IsExist(err) { + os.Remove(logpath) + } + } + return nil +} + +func (vd *VDDKDisk) fuseUmount(path string) { + maxTries, tried := 4, 0 + + _, err := os.Stat(path) + if err != nil && os.IsNotExist(err) { + // no such path + return + } + + for tried < maxTries { + tried += 1 + err := exec.Command("umount", path).Run() + if err != nil { + time.Sleep(time.Duration(tried) * 15 * time.Second) + log.Errorf("Fail to umount %s: %s", path, err) + continue + } + _, err = os.Stat(path) + if err == nil || os.IsExist(err) { + err = exec.Command("rm", "-rf", path).Run() + if err != nil { + time.Sleep(time.Duration(tried) * 15 * time.Second) + log.Errorf("Fail to umount %s: %s", path, err) + continue + } + } + } +} + +func (vd *VDDKDisk) ExecProg() error { + thumb, err := vd.getServerCertThumbSha1(fmt.Sprintf("%s:%d", vd.Host, vd.Port)) + if err != nil { + return errors.Wrapf(err, "Fail contact server %s", vd.Host) + } + cmd := NewCommand(execpath(), "-info", "-host", vd.Host, "-port", strconv.Itoa(vd.Port), "-user", vd.User, + "-password", vd.Passwd, "-mode", "nbd", "-thumb", thumb, "-vm", fmt.Sprintf("moref=%s", vd.VmRef), vd.DiskPath) + env := os.Environ() + env = append(env, fmt.Sprintf("LD_LIBRARY_PATH=%s", libdir())) + cmd.Env = env + vd.Proc = cmd + err = vd.Proc.Start() + if err != nil { + return errors.Wrap(err, "vd.Proc.Start") + } + vd.Pid = cmd.Process.Pid + return nil +} + +// getServerCertBin try to obtain the remote ssl certificate +func (vd *VDDKDisk) getServerCertBin(addr string) ([]byte, error) { + rawConn, err := net.Dial("tcp", addr) + if err != nil { + return nil, errors.Wrapf(err, "net.Dial for addr '%s'", addr) + } + defer rawConn.Close() + + // get the wrpped conn + sslWrappedConn := tls.Client(rawConn, &tls.Config{InsecureSkipVerify: true}) + err = sslWrappedConn.Handshake() + if err != nil { + return nil, errors.Wrapf(err, "fail to complete ssl handshake with addr '%s'", addr) + } + return sslWrappedConn.ConnectionState().PeerCertificates[0].Raw, nil +} + +func (vd *VDDKDisk) getServerCertThumbSha1(addr string) (string, error) { + certBin, err := vd.getServerCertBin(addr) + if err != nil { + return "", err + } + sha := sha1.Sum(certBin) + shaHex := hex.EncodeToString(sha[:]) + length := len(shaHex) / 2 * 2 + tmp := make([][]byte, 0, length) + for i := 1; i < length; i += 2 { + tmp = append(tmp, []byte{shaHex[i-1], shaHex[i]}) + } + return string(bytes.Join(tmp, []byte{':'})), nil +} + +func (vd *VDDKDisk) WaitMounted() error { + endStr := []byte("Do you want to procede to unmount the volume") + timeout := 30 * time.Second + endClock := time.After(timeout) + isEnd := false + +Loop: + for !vd.Proc.Exited() { + select { + case <-endClock: + break Loop + default: + if bytes.Contains(vd.Proc.stdouterr.Bytes(), endStr) { + log.Debugf("find the mark") + isEnd = true + break Loop + } + } + // Reduce inspection density + time.Sleep(100 * time.Millisecond) + } + + backup := vd.Proc.stdouterr.String() + err := vd.ParsePartitions(backup) + if err != nil { + return errors.Wrap(err, "VDDKDisk.ParsePartitions") + } + if vd.Proc.Exited() { + retCode := vd.Proc.ProcessState.ExitCode() + // ignore the error + vd.Proc.Kill() + vd.Proc = nil + return errors.Error(fmt.Sprintf("VDDKDisk prog exit error(%d): %s", retCode, backup)) + } else if !isEnd { + // timeout + vd.Proc.Kill() + return errors.Error(fmt.Sprintf("VDDKDisk read timeout, program blocked")) + } + return nil +} + +type VDDKPartition struct { + *guestfs.SLocalGuestFS +} + +func (vp *VDDKPartition) Mount() bool { + log.Debugf("VDDKPartition.Mount not implement") + return true +} + +func (vp *VDDKPartition) MountPartReadOnly() bool { + log.Debugf("VDDKPartition.MountPartReadOnly not implement") + return true +} + +func (vp *VDDKPartition) Umount() bool { + log.Debugf("VDDKPartition.Umount not implement") + return true +} + +func (vp *VDDKPartition) IsReadonly() bool { + return guestfs.IsPartitionReadonly(vp) +} + +func (vp *VDDKPartition) GetPhysicalPartitionType() string { + log.Debugf("VDDKPartition.GetPhysicalPartitionType not implement") + return "" +} + +func newVDDKPartition(mntPath string) *VDDKPartition { + return &VDDKPartition{guestfs.NewLocalGuestFS(mntPath)} +} diff --git a/pkg/hostman/hostdeployer/apis/deploy.pb.go b/pkg/hostman/hostdeployer/apis/deploy.pb.go index e420bc0600..2c163dcd60 100644 --- a/pkg/hostman/hostdeployer/apis/deploy.pb.go +++ b/pkg/hostman/hostdeployer/apis/deploy.pb.go @@ -46,6 +46,7 @@ type GuestDesc struct { Nics []*Nic `protobuf:"bytes,4,rep,name=nics,proto3" json:"nics,omitempty"` NicsStandby []*Nic `protobuf:"bytes,5,rep,name=nics_standby,json=nicsStandby,proto3" json:"nics_standby,omitempty"` Disks []*Disk `protobuf:"bytes,6,rep,name=disks,proto3" json:"disks,omitempty"` + Hypervisor string `protobuf:"bytes,7,opt,name=Hypervisor,proto3" json:"Hypervisor,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -118,6 +119,13 @@ func (m *GuestDesc) GetDisks() []*Disk { return nil } +func (m *GuestDesc) GetHypervisor() string { + if m != nil { + return m.Hypervisor + } + return "" +} + type Disk struct { DiskId string `protobuf:"bytes,1,opt,name=disk_id,json=diskId,proto3" json:"disk_id,omitempty"` Driver string `protobuf:"bytes,2,opt,name=driver,proto3" json:"driver,omitempty"` @@ -524,6 +532,77 @@ func (m *Nic) GetName() string { return "" } +type VDDKConInfo struct { + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + User string `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"` + Passwd string `protobuf:"bytes,4,opt,name=passwd,proto3" json:"passwd,omitempty"` + Vmref string `protobuf:"bytes,5,opt,name=vmref,proto3" json:"vmref,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *VDDKConInfo) Reset() { *m = VDDKConInfo{} } +func (m *VDDKConInfo) String() string { return proto.CompactTextString(m) } +func (*VDDKConInfo) ProtoMessage() {} +func (*VDDKConInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_05f09e103004e384, []int{3} +} + +func (m *VDDKConInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_VDDKConInfo.Unmarshal(m, b) +} +func (m *VDDKConInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_VDDKConInfo.Marshal(b, m, deterministic) +} +func (m *VDDKConInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_VDDKConInfo.Merge(m, src) +} +func (m *VDDKConInfo) XXX_Size() int { + return xxx_messageInfo_VDDKConInfo.Size(m) +} +func (m *VDDKConInfo) XXX_DiscardUnknown() { + xxx_messageInfo_VDDKConInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_VDDKConInfo proto.InternalMessageInfo + +func (m *VDDKConInfo) GetHost() string { + if m != nil { + return m.Host + } + return "" +} + +func (m *VDDKConInfo) GetPort() int32 { + if m != nil { + return m.Port + } + return 0 +} + +func (m *VDDKConInfo) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +func (m *VDDKConInfo) GetPasswd() string { + if m != nil { + return m.Passwd + } + return "" +} + +func (m *VDDKConInfo) GetVmref() string { + if m != nil { + return m.Vmref + } + return "" +} + type DeployInfo struct { PublicKey *SSHKeys `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` Deploys []*DeployContent `protobuf:"bytes,2,rep,name=deploys,proto3" json:"deploys,omitempty"` @@ -542,7 +621,7 @@ func (m *DeployInfo) Reset() { *m = DeployInfo{} } func (m *DeployInfo) String() string { return proto.CompactTextString(m) } func (*DeployInfo) ProtoMessage() {} func (*DeployInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{3} + return fileDescriptor_05f09e103004e384, []int{4} } func (m *DeployInfo) XXX_Unmarshal(b []byte) error { @@ -633,7 +712,7 @@ func (m *SSHKeys) Reset() { *m = SSHKeys{} } func (m *SSHKeys) String() string { return proto.CompactTextString(m) } func (*SSHKeys) ProtoMessage() {} func (*SSHKeys) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{4} + return fileDescriptor_05f09e103004e384, []int{5} } func (m *SSHKeys) XXX_Unmarshal(b []byte) error { @@ -695,7 +774,7 @@ func (m *DeployContent) Reset() { *m = DeployContent{} } func (m *DeployContent) String() string { return proto.CompactTextString(m) } func (*DeployContent) ProtoMessage() {} func (*DeployContent) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{5} + return fileDescriptor_05f09e103004e384, []int{6} } func (m *DeployContent) XXX_Unmarshal(b []byte) error { @@ -747,7 +826,7 @@ func (m *Empty) Reset() { *m = Empty{} } func (m *Empty) String() string { return proto.CompactTextString(m) } func (*Empty) ProtoMessage() {} func (*Empty) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{6} + return fileDescriptor_05f09e103004e384, []int{7} } func (m *Empty) XXX_Unmarshal(b []byte) error { @@ -785,7 +864,7 @@ func (m *DeployGuestFsResponse) Reset() { *m = DeployGuestFsResponse{} } func (m *DeployGuestFsResponse) String() string { return proto.CompactTextString(m) } func (*DeployGuestFsResponse) ProtoMessage() {} func (*DeployGuestFsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{7} + return fileDescriptor_05f09e103004e384, []int{8} } func (m *DeployGuestFsResponse) XXX_Unmarshal(b []byte) error { @@ -856,19 +935,20 @@ func (m *DeployGuestFsResponse) GetKey() string { } type DeployParams struct { - DiskPath string `protobuf:"bytes,1,opt,name=disk_path,json=diskPath,proto3" json:"disk_path,omitempty"` - GuestDesc *GuestDesc `protobuf:"bytes,2,opt,name=guest_desc,json=guestDesc,proto3" json:"guest_desc,omitempty"` - DeployInfo *DeployInfo `protobuf:"bytes,3,opt,name=deploy_info,json=deployInfo,proto3" json:"deploy_info,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DiskPath string `protobuf:"bytes,1,opt,name=disk_path,json=diskPath,proto3" json:"disk_path,omitempty"` + GuestDesc *GuestDesc `protobuf:"bytes,2,opt,name=guest_desc,json=guestDesc,proto3" json:"guest_desc,omitempty"` + DeployInfo *DeployInfo `protobuf:"bytes,3,opt,name=deploy_info,json=deployInfo,proto3" json:"deploy_info,omitempty"` + VddkInfo *VDDKConInfo `protobuf:"bytes,4,opt,name=vddk_info,json=vddkInfo,proto3" json:"vddk_info,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *DeployParams) Reset() { *m = DeployParams{} } func (m *DeployParams) String() string { return proto.CompactTextString(m) } func (*DeployParams) ProtoMessage() {} func (*DeployParams) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{8} + return fileDescriptor_05f09e103004e384, []int{9} } func (m *DeployParams) XXX_Unmarshal(b []byte) error { @@ -910,6 +990,13 @@ func (m *DeployParams) GetDeployInfo() *DeployInfo { return nil } +func (m *DeployParams) GetVddkInfo() *VDDKConInfo { + if m != nil { + return m.VddkInfo + } + return nil +} + type ResizeFsParams struct { DiskPath string `protobuf:"bytes,1,opt,name=disk_path,json=diskPath,proto3" json:"disk_path,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -921,7 +1008,7 @@ func (m *ResizeFsParams) Reset() { *m = ResizeFsParams{} } func (m *ResizeFsParams) String() string { return proto.CompactTextString(m) } func (*ResizeFsParams) ProtoMessage() {} func (*ResizeFsParams) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{9} + return fileDescriptor_05f09e103004e384, []int{10} } func (m *ResizeFsParams) XXX_Unmarshal(b []byte) error { @@ -962,7 +1049,7 @@ func (m *FormatFsParams) Reset() { *m = FormatFsParams{} } func (m *FormatFsParams) String() string { return proto.CompactTextString(m) } func (*FormatFsParams) ProtoMessage() {} func (*FormatFsParams) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{10} + return fileDescriptor_05f09e103004e384, []int{11} } func (m *FormatFsParams) XXX_Unmarshal(b []byte) error { @@ -1018,7 +1105,7 @@ func (m *ReleaseInfo) Reset() { *m = ReleaseInfo{} } func (m *ReleaseInfo) String() string { return proto.CompactTextString(m) } func (*ReleaseInfo) ProtoMessage() {} func (*ReleaseInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{11} + return fileDescriptor_05f09e103004e384, []int{12} } func (m *ReleaseInfo) XXX_Unmarshal(b []byte) error { @@ -1079,7 +1166,7 @@ func (m *SaveToGlanceParams) Reset() { *m = SaveToGlanceParams{} } func (m *SaveToGlanceParams) String() string { return proto.CompactTextString(m) } func (*SaveToGlanceParams) ProtoMessage() {} func (*SaveToGlanceParams) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{12} + return fileDescriptor_05f09e103004e384, []int{13} } func (m *SaveToGlanceParams) XXX_Unmarshal(b []byte) error { @@ -1126,7 +1213,7 @@ func (m *SaveToGlanceResponse) Reset() { *m = SaveToGlanceResponse{} } func (m *SaveToGlanceResponse) String() string { return proto.CompactTextString(m) } func (*SaveToGlanceResponse) ProtoMessage() {} func (*SaveToGlanceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{13} + return fileDescriptor_05f09e103004e384, []int{14} } func (m *SaveToGlanceResponse) XXX_Unmarshal(b []byte) error { @@ -1172,7 +1259,7 @@ func (m *ProbeImageInfoPramas) Reset() { *m = ProbeImageInfoPramas{} } func (m *ProbeImageInfoPramas) String() string { return proto.CompactTextString(m) } func (*ProbeImageInfoPramas) ProtoMessage() {} func (*ProbeImageInfoPramas) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{14} + return fileDescriptor_05f09e103004e384, []int{15} } func (m *ProbeImageInfoPramas) XXX_Unmarshal(b []byte) error { @@ -1217,7 +1304,7 @@ func (m *ImageInfo) Reset() { *m = ImageInfo{} } func (m *ImageInfo) String() string { return proto.CompactTextString(m) } func (*ImageInfo) ProtoMessage() {} func (*ImageInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_05f09e103004e384, []int{15} + return fileDescriptor_05f09e103004e384, []int{16} } func (m *ImageInfo) XXX_Unmarshal(b []byte) error { @@ -1291,6 +1378,7 @@ func init() { proto.RegisterType((*GuestDesc)(nil), "apis.GuestDesc") proto.RegisterType((*Disk)(nil), "apis.Disk") proto.RegisterType((*Nic)(nil), "apis.Nic") + proto.RegisterType((*VDDKConInfo)(nil), "apis.VDDKConInfo") proto.RegisterType((*DeployInfo)(nil), "apis.DeployInfo") proto.RegisterType((*SSHKeys)(nil), "apis.SSHKeys") proto.RegisterType((*DeployContent)(nil), "apis.DeployContent") @@ -1309,102 +1397,107 @@ func init() { func init() { proto.RegisterFile("deploy.proto", fileDescriptor_05f09e103004e384) } var fileDescriptor_05f09e103004e384 = []byte{ - // 1512 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xdd, 0x6e, 0x1b, 0x37, - 0x16, 0x86, 0xfe, 0x47, 0x47, 0xb6, 0x6c, 0x33, 0xfe, 0x99, 0x28, 0x9b, 0x8d, 0x21, 0x60, 0x17, - 0x46, 0x90, 0x18, 0x58, 0x67, 0x77, 0x6f, 0x16, 0x0b, 0x6c, 0x10, 0x6f, 0x52, 0x21, 0x4d, 0x60, - 0x8c, 0x63, 0xf4, 0xae, 0x03, 0x6a, 0x86, 0x92, 0x59, 0xcf, 0x90, 0x03, 0x92, 0x63, 0x45, 0x7d, - 0x88, 0x3e, 0x49, 0xaf, 0x02, 0xf4, 0xa6, 0x8f, 0xd1, 0xbb, 0xbe, 0x42, 0x9f, 0xa2, 0x38, 0x24, - 0x67, 0x34, 0x4e, 0x02, 0x34, 0x37, 0xbd, 0x12, 0xcf, 0x77, 0x0e, 0xc9, 0xc3, 0xf3, 0xf3, 0x9d, - 0x11, 0x6c, 0xa5, 0xac, 0xc8, 0xe4, 0xfa, 0xb4, 0x50, 0xd2, 0x48, 0xd2, 0xa5, 0x05, 0xd7, 0xd3, - 0x9f, 0x5b, 0x30, 0x7c, 0x55, 0x32, 0x6d, 0xce, 0x99, 0x4e, 0x08, 0x81, 0xae, 0xa0, 0x39, 0x0b, - 0x5b, 0xc7, 0xad, 0x93, 0x61, 0x64, 0xd7, 0x88, 0x95, 0x25, 0x4f, 0xc3, 0xb6, 0xc3, 0x70, 0x4d, - 0x0e, 0xa1, 0x9f, 0xca, 0x9c, 0x72, 0x11, 0x76, 0x2c, 0xea, 0x25, 0xf2, 0x10, 0xba, 0x82, 0x27, - 0x3a, 0xec, 0x1e, 0x77, 0x4e, 0x46, 0x67, 0xc3, 0x53, 0xbc, 0xe2, 0xf4, 0x2d, 0x4f, 0x22, 0x0b, - 0x93, 0x27, 0xb0, 0x85, 0xbf, 0xb1, 0x36, 0x54, 0xa4, 0xf3, 0x75, 0xd8, 0xfb, 0xd8, 0x6c, 0x84, - 0xea, 0x4b, 0xa7, 0x25, 0xc7, 0xd0, 0x4b, 0xb9, 0xbe, 0xd1, 0x61, 0xdf, 0x9a, 0x81, 0x33, 0x3b, - 0xe7, 0xfa, 0x26, 0x72, 0x8a, 0xe9, 0xaf, 0x1d, 0xe8, 0xa2, 0x4c, 0x8e, 0x60, 0x80, 0x48, 0xcc, - 0x53, 0xef, 0x7a, 0x1f, 0xc5, 0x99, 0x73, 0x54, 0xf1, 0x5b, 0xa6, 0xbc, 0xfb, 0x5e, 0x22, 0x0f, - 0x01, 0x12, 0x9a, 0x5c, 0xb3, 0x38, 0x97, 0x29, 0xf3, 0x8f, 0x18, 0x5a, 0xe4, 0x8d, 0x4c, 0x19, - 0xb9, 0x0f, 0x01, 0xe5, 0xd2, 0x29, 0xbb, 0x56, 0x39, 0xa0, 0x5c, 0x5a, 0x15, 0x81, 0xae, 0xe6, - 0xdf, 0xb3, 0xb0, 0x77, 0xdc, 0x3a, 0xe9, 0x44, 0x76, 0x4d, 0x1e, 0xc1, 0xc8, 0xb0, 0xbc, 0xc8, - 0xa8, 0x61, 0xe8, 0x42, 0xdf, 0xee, 0x80, 0x0a, 0x9a, 0xa5, 0x78, 0x1d, 0xcf, 0xe9, 0x92, 0xc5, - 0x05, 0x35, 0xd7, 0xe1, 0xc0, 0x5d, 0x67, 0x91, 0x0b, 0x6a, 0xae, 0x51, 0xad, 0x8d, 0x54, 0x68, - 0xc0, 0xd3, 0x30, 0x70, 0x6a, 0x8f, 0xcc, 0x52, 0xf2, 0x17, 0x18, 0xe6, 0x7c, 0xa9, 0xa8, 0xe1, - 0x62, 0x19, 0x0e, 0x8f, 0x5b, 0x27, 0x41, 0xb4, 0x01, 0xc8, 0x63, 0xd8, 0x33, 0x54, 0x2d, 0x99, - 0x89, 0x1b, 0x67, 0x80, 0x3d, 0x63, 0xc7, 0x29, 0x2e, 0xeb, 0x93, 0x08, 0x74, 0xad, 0x07, 0x23, - 0x97, 0x4b, 0x5c, 0x63, 0x88, 0x16, 0x52, 0xe5, 0xd4, 0x84, 0x5b, 0x2e, 0x44, 0x4e, 0x22, 0xfb, - 0xd0, 0xe3, 0x22, 0x65, 0xef, 0xc3, 0xed, 0xe3, 0xd6, 0x49, 0x2f, 0x72, 0x02, 0xf9, 0x1b, 0x8c, - 0x73, 0xa6, 0x96, 0x2c, 0xd6, 0x82, 0x16, 0xfa, 0x5a, 0x9a, 0x70, 0x6c, 0x1d, 0xda, 0xb6, 0xe8, - 0xa5, 0x07, 0xc9, 0x18, 0xda, 0x0b, 0x1d, 0xee, 0xd8, 0x03, 0xdb, 0x0b, 0x4d, 0xfe, 0x0a, 0x90, - 0xcb, 0x52, 0x98, 0x42, 0x72, 0x61, 0xc2, 0x5d, 0x17, 0xa0, 0x0d, 0x42, 0x76, 0xa1, 0x93, 0xb2, - 0xdb, 0x70, 0xcf, 0x2a, 0x70, 0x39, 0xfd, 0xad, 0x0b, 0x9d, 0xb7, 0x3c, 0x41, 0x4d, 0x4e, 0x13, - 0x9f, 0x56, 0x5c, 0xe2, 0xd9, 0xbc, 0xf0, 0xf9, 0x6c, 0xf3, 0x02, 0x2d, 0x04, 0x33, 0x3e, 0x89, - 0xb8, 0x24, 0x07, 0xd0, 0x17, 0xcc, 0x60, 0x1c, 0x5c, 0xf2, 0x7a, 0x82, 0x99, 0x59, 0x4a, 0x42, - 0x18, 0xdc, 0x72, 0x65, 0x4a, 0x9a, 0xd9, 0xec, 0x05, 0x51, 0x25, 0xa2, 0x66, 0x49, 0x0d, 0x5b, - 0xd1, 0xb5, 0x4f, 0x5e, 0x25, 0x5a, 0xc7, 0x84, 0xf6, 0x29, 0xc3, 0x65, 0xa3, 0xf6, 0x83, 0x3b, - 0xb5, 0x7f, 0x08, 0x7d, 0x25, 0x4b, 0xc3, 0xb4, 0x4d, 0xd1, 0x30, 0xf2, 0x12, 0xe2, 0x7c, 0x61, - 0xbb, 0xca, 0x25, 0xc5, 0x4b, 0x78, 0x67, 0x4e, 0xf5, 0x4d, 0xc6, 0x84, 0x4d, 0x47, 0x2f, 0xaa, - 0xc4, 0x46, 0xd1, 0x6e, 0xdd, 0x29, 0xda, 0x43, 0xe8, 0xcf, 0x15, 0x4f, 0x97, 0xcc, 0xa6, 0x64, - 0x18, 0x79, 0x09, 0xab, 0x7f, 0xc5, 0x95, 0xcd, 0xfb, 0xd8, 0x29, 0x50, 0x74, 0xe9, 0xbe, 0xcd, - 0xa8, 0xb0, 0x79, 0xe8, 0x45, 0x76, 0x8d, 0xc5, 0xc4, 0x85, 0x61, 0x6a, 0x41, 0x13, 0xe6, 0x13, - 0xb1, 0x01, 0x30, 0xb6, 0xf3, 0x95, 0x4d, 0x43, 0x2f, 0x6a, 0xcf, 0x57, 0x9b, 0x22, 0x20, 0xcd, - 0x22, 0x78, 0x04, 0x23, 0x1f, 0xb9, 0x98, 0x17, 0x3a, 0xbc, 0x77, 0xdc, 0xc1, 0x74, 0x7a, 0x68, - 0x56, 0x68, 0x34, 0x60, 0xef, 0x0d, 0x53, 0x82, 0x65, 0xe8, 0xd5, 0xbe, 0xcb, 0x77, 0x05, 0xcd, - 0x52, 0xf2, 0x00, 0x86, 0x86, 0xd1, 0x3c, 0x5e, 0x71, 0x73, 0x1d, 0x1e, 0x58, 0x75, 0x80, 0xc0, - 0x37, 0xdc, 0x55, 0x64, 0x4e, 0x05, 0xa6, 0xe9, 0xd0, 0xa6, 0xc9, 0x4b, 0xd8, 0x95, 0x82, 0x27, - 0xb1, 0x59, 0x17, 0x2c, 0x3c, 0x72, 0x69, 0x12, 0x3c, 0x79, 0xb7, 0x2e, 0x6c, 0x08, 0x32, 0x2e, - 0x6e, 0xe2, 0xb2, 0x08, 0x43, 0xb7, 0x07, 0xc5, 0x2b, 0x5b, 0x1c, 0xb9, 0x29, 0xc3, 0xfb, 0xb6, - 0x5b, 0x71, 0x59, 0x73, 0xdc, 0x64, 0xc3, 0x71, 0xd3, 0x5f, 0xda, 0x00, 0xe7, 0x96, 0x1c, 0x67, - 0x62, 0x21, 0xc9, 0x13, 0x80, 0xa2, 0x9c, 0x67, 0x3c, 0x89, 0x6f, 0xd8, 0xda, 0x96, 0xde, 0xe8, - 0x6c, 0xdb, 0xd1, 0xcf, 0xe5, 0xe5, 0x57, 0xaf, 0xd9, 0x5a, 0x47, 0x43, 0x67, 0xf0, 0x9a, 0xad, - 0xc9, 0x53, 0x18, 0x38, 0x62, 0xd5, 0x61, 0xdb, 0x32, 0xd5, 0x3d, 0xcf, 0x54, 0x16, 0x7c, 0x21, - 0x85, 0x61, 0xc2, 0x44, 0x95, 0x0d, 0x99, 0x40, 0x50, 0x50, 0xad, 0x57, 0x52, 0xa5, 0xbe, 0x66, - 0x6b, 0x19, 0x9f, 0xc1, 0x75, 0xcc, 0x05, 0x37, 0xb6, 0x72, 0x83, 0xa8, 0xcf, 0xf5, 0x4c, 0x70, - 0x83, 0x0c, 0xc1, 0x04, 0x9d, 0x67, 0x2c, 0x36, 0x66, 0xed, 0xab, 0x77, 0xe8, 0x90, 0x77, 0x66, - 0x8d, 0x1c, 0x90, 0xb2, 0x05, 0x2d, 0x33, 0x13, 0x2b, 0x29, 0x4d, 0x5c, 0x6a, 0xa6, 0x6c, 0x25, - 0x07, 0xd1, 0x8e, 0x57, 0x44, 0x52, 0x9a, 0x2b, 0xcd, 0x14, 0xf9, 0x0f, 0x4c, 0x56, 0x5c, 0xa4, - 0x72, 0xa5, 0xe3, 0x6a, 0x0f, 0x4d, 0x73, 0x2e, 0xdc, 0xa6, 0x81, 0xdd, 0x74, 0xe4, 0x2d, 0xce, - 0x9d, 0xc1, 0x73, 0xd4, 0xdb, 0xcd, 0x8f, 0x61, 0xcf, 0xfb, 0x91, 0x64, 0xb2, 0x4c, 0x9d, 0xab, - 0x81, 0xbb, 0xc8, 0x29, 0x5e, 0x20, 0x8e, 0x3e, 0x4f, 0x7f, 0x6c, 0xc1, 0xc0, 0x87, 0x0b, 0xfd, - 0xff, 0x28, 0xa2, 0xc3, 0x66, 0x08, 0xad, 0xff, 0x19, 0x33, 0x2c, 0x6e, 0x58, 0xb9, 0x0e, 0xdf, - 0x71, 0x8a, 0x8b, 0xda, 0xf6, 0x04, 0x76, 0x9d, 0xbf, 0x0d, 0x53, 0x17, 0xc7, 0xb1, 0xc5, 0x37, - 0x96, 0x4f, 0x80, 0x14, 0x4a, 0x7e, 0xc7, 0x12, 0xd3, 0xb4, 0x75, 0x94, 0xb0, 0xeb, 0x35, 0xb5, - 0xf5, 0xf4, 0x0a, 0xb6, 0xef, 0x64, 0xac, 0x26, 0xcb, 0x56, 0x83, 0x2c, 0x43, 0x18, 0x24, 0x4e, - 0xed, 0xdd, 0xab, 0x44, 0x2c, 0x5a, 0x9a, 0x18, 0x2e, 0xeb, 0x91, 0xe8, 0xa4, 0xe9, 0x00, 0x7a, - 0xff, 0xcf, 0x0b, 0xb3, 0x9e, 0xfe, 0xd4, 0x82, 0x03, 0x77, 0x81, 0x9d, 0xb7, 0x2f, 0x75, 0xc4, - 0x74, 0x21, 0x85, 0x66, 0xb6, 0xdf, 0xb9, 0x36, 0x4a, 0x36, 0x86, 0x97, 0x51, 0xd2, 0xf2, 0x15, - 0x53, 0x1a, 0xcf, 0xf4, 0x97, 0x79, 0x11, 0x5d, 0xa3, 0x2a, 0xb9, 0xf6, 0x57, 0xd9, 0x35, 0xd6, - 0x55, 0x46, 0xc5, 0xb2, 0xa4, 0xcb, 0x6a, 0x66, 0xd5, 0x32, 0xb6, 0xb5, 0xd4, 0xb6, 0x6c, 0x86, - 0x51, 0x5b, 0x6a, 0x3c, 0x99, 0x26, 0x09, 0xd2, 0x6f, 0xc5, 0x77, 0x5e, 0xc4, 0x7e, 0xc1, 0x20, - 0x79, 0xbe, 0xbb, 0x61, 0xeb, 0xe9, 0x0f, 0x2d, 0xd8, 0x72, 0x7e, 0x5f, 0x50, 0x45, 0x73, 0x8d, - 0xbd, 0x6b, 0x87, 0x6d, 0x23, 0x38, 0x01, 0x02, 0x76, 0x94, 0x9d, 0x02, 0x2c, 0xf1, 0x79, 0x71, - 0xca, 0x74, 0x62, 0xdd, 0x1e, 0x9d, 0xed, 0xb8, 0x7e, 0xa8, 0x3f, 0x33, 0xa2, 0xe1, 0xb2, 0xfe, - 0xe2, 0xf8, 0x07, 0x8c, 0x5c, 0x63, 0xc4, 0x5c, 0x2c, 0xa4, 0x7d, 0xd0, 0xe8, 0x6c, 0xb7, 0xd9, - 0x40, 0xd8, 0x91, 0x11, 0xa4, 0xf5, 0x7a, 0xfa, 0x14, 0xc6, 0x11, 0xc3, 0xb9, 0xfb, 0x52, 0x7f, - 0x81, 0x47, 0xd3, 0x6f, 0x61, 0xfc, 0xd2, 0x4e, 0xb4, 0x2f, 0x32, 0x47, 0xe5, 0x42, 0xc7, 0x7e, - 0x22, 0xba, 0xb0, 0x07, 0x0b, 0xed, 0x4e, 0xa8, 0xbf, 0x85, 0x3a, 0x9b, 0x6f, 0xa1, 0xa9, 0x84, - 0x51, 0xc4, 0x32, 0x46, 0x35, 0xb3, 0xdc, 0xf1, 0xa7, 0x27, 0x73, 0xfa, 0x06, 0xc8, 0x25, 0xbd, - 0x65, 0xef, 0xe4, 0xab, 0x8c, 0x8a, 0x84, 0x7d, 0xc9, 0xa3, 0x26, 0x10, 0x24, 0x32, 0x2f, 0x14, - 0xd3, 0xda, 0xde, 0x1e, 0x44, 0xb5, 0x3c, 0x65, 0xb0, 0xdf, 0x3c, 0xae, 0xae, 0xca, 0x23, 0x18, - 0x48, 0xed, 0xb2, 0xe2, 0x5f, 0x22, 0xb5, 0x7d, 0xe1, 0x3f, 0x61, 0x4b, 0xb9, 0x07, 0x3b, 0xad, - 0x4b, 0xf2, 0x9e, 0xcb, 0x59, 0x23, 0x14, 0xd1, 0x48, 0x6d, 0x84, 0xe9, 0x33, 0xd8, 0xbf, 0x50, - 0x72, 0xce, 0x66, 0xf8, 0xd5, 0x83, 0xc8, 0x85, 0xa2, 0x39, 0xfd, 0x83, 0xdc, 0x7d, 0x68, 0xc3, - 0xb0, 0xde, 0x40, 0x1e, 0xdf, 0xf5, 0xe8, 0xb3, 0x77, 0x56, 0x4e, 0x3a, 0xef, 0xed, 0xa8, 0x68, - 0x57, 0xde, 0xdb, 0x49, 0xf1, 0x77, 0xd8, 0xe1, 0x3a, 0x2e, 0xd9, 0x82, 0xc7, 0xba, 0x2c, 0x0a, - 0xa9, 0xdc, 0x97, 0x43, 0x10, 0x6d, 0x73, 0x7d, 0xc5, 0x16, 0xfc, 0xd2, 0x81, 0x48, 0x33, 0x5c, - 0xc7, 0xd9, 0x6d, 0x1e, 0x17, 0x54, 0x19, 0x6e, 0x3b, 0xdb, 0x71, 0xf2, 0x98, 0xeb, 0xaf, 0x6f, - 0xf3, 0x8b, 0x0a, 0xc5, 0x61, 0xc7, 0x75, 0xac, 0x18, 0x4d, 0xa5, 0xc8, 0x2a, 0x72, 0x06, 0xae, - 0x23, 0x8f, 0x90, 0x7f, 0xc3, 0x51, 0x71, 0xbd, 0xd6, 0x3c, 0xa1, 0xd9, 0xe6, 0x30, 0xe7, 0x9b, - 0xeb, 0xbe, 0x83, 0x4a, 0x5d, 0x1f, 0x6a, 0x5d, 0xfd, 0x17, 0x1c, 0xd9, 0x69, 0xa0, 0x0d, 0xcd, - 0x32, 0x96, 0x36, 0x29, 0xd7, 0xd1, 0xf4, 0x3e, 0x4e, 0x07, 0xaf, 0xad, 0x79, 0xf7, 0xec, 0x43, - 0x1b, 0x46, 0xae, 0x75, 0x9e, 0x2f, 0x91, 0x99, 0xfe, 0x57, 0x11, 0x9b, 0xe7, 0x1d, 0x42, 0x9a, - 0xed, 0xe5, 0xca, 0x67, 0xf2, 0xa0, 0x89, 0x7d, 0x4c, 0x50, 0x4f, 0x21, 0xa8, 0x3a, 0x8e, 0xec, - 0x57, 0x31, 0x6f, 0x76, 0xe0, 0x64, 0xe4, 0x50, 0xcb, 0x74, 0x68, 0x5e, 0x75, 0x5c, 0x65, 0x7e, - 0xb7, 0x03, 0xef, 0x9a, 0x9f, 0xc3, 0x56, 0xb3, 0x00, 0x49, 0xe8, 0x27, 0xed, 0x27, 0x35, 0x3e, - 0x99, 0x7c, 0xaa, 0xa9, 0x7d, 0xfc, 0x2f, 0x8c, 0xef, 0xd6, 0x17, 0xf1, 0xd6, 0x9f, 0xab, 0xba, - 0x89, 0xa7, 0xa4, 0x1a, 0x9e, 0xf7, 0xed, 0x9f, 0xa2, 0x67, 0xbf, 0x07, 0x00, 0x00, 0xff, 0xff, - 0xdf, 0xa7, 0xb1, 0x31, 0x24, 0x0d, 0x00, 0x00, + // 1591 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcd, 0x6e, 0x23, 0xb9, + 0x11, 0x86, 0xfe, 0xd5, 0x25, 0x8f, 0xec, 0xe1, 0x7a, 0xc6, 0xbd, 0xda, 0x6c, 0xd6, 0x10, 0x90, + 0xc0, 0x18, 0xcc, 0x18, 0x88, 0x37, 0xc9, 0x25, 0x08, 0x90, 0xc5, 0x38, 0xb3, 0x2b, 0x4c, 0x76, + 0x61, 0xb4, 0xc7, 0xc9, 0x2d, 0x0d, 0xba, 0x9b, 0x92, 0x18, 0x77, 0x93, 0x0d, 0x92, 0x92, 0x56, + 0x79, 0xa6, 0x9c, 0x16, 0xc8, 0x35, 0x0f, 0x91, 0x5b, 0xf2, 0x08, 0x79, 0x8a, 0xa0, 0x8a, 0xec, + 0x56, 0x7b, 0x76, 0x80, 0xcc, 0x25, 0x27, 0x55, 0x7d, 0x55, 0x24, 0x8b, 0xf5, 0xf3, 0xb1, 0x05, + 0x47, 0xb9, 0xa8, 0x0a, 0xbd, 0xbf, 0xac, 0x8c, 0x76, 0x9a, 0xf5, 0x79, 0x25, 0xed, 0xfc, 0xdf, + 0x1d, 0x88, 0xbe, 0xde, 0x08, 0xeb, 0xae, 0x85, 0xcd, 0x18, 0x83, 0xbe, 0xe2, 0xa5, 0x88, 0x3b, + 0xe7, 0x9d, 0x8b, 0x28, 0x21, 0x19, 0xb1, 0xcd, 0x46, 0xe6, 0x71, 0xd7, 0x63, 0x28, 0xb3, 0xe7, + 0x30, 0xcc, 0x75, 0xc9, 0xa5, 0x8a, 0x7b, 0x84, 0x06, 0x8d, 0x7d, 0x0e, 0x7d, 0x25, 0x33, 0x1b, + 0xf7, 0xcf, 0x7b, 0x17, 0x93, 0xab, 0xe8, 0x12, 0x8f, 0xb8, 0xfc, 0x4e, 0x66, 0x09, 0xc1, 0xec, + 0x25, 0x1c, 0xe1, 0x6f, 0x6a, 0x1d, 0x57, 0xf9, 0xfd, 0x3e, 0x1e, 0xbc, 0xef, 0x36, 0x41, 0xf3, + 0xad, 0xb7, 0xb2, 0x73, 0x18, 0xe4, 0xd2, 0x3e, 0xd8, 0x78, 0x48, 0x6e, 0xe0, 0xdd, 0xae, 0xa5, + 0x7d, 0x48, 0xbc, 0x81, 0xfd, 0x14, 0xe0, 0x9b, 0x7d, 0x25, 0xcc, 0x56, 0x5a, 0x6d, 0xe2, 0x11, + 0x85, 0xd2, 0x42, 0xe6, 0xff, 0xea, 0x41, 0x1f, 0xfd, 0xd9, 0x19, 0x8c, 0x70, 0x45, 0x2a, 0xf3, + 0x70, 0xb5, 0x21, 0xaa, 0x0b, 0x7f, 0x11, 0x23, 0xb7, 0xc2, 0x84, 0xeb, 0x05, 0x8d, 0x7d, 0x0e, + 0x90, 0xf1, 0x6c, 0x2d, 0xd2, 0x52, 0xe7, 0x22, 0x5c, 0x32, 0x22, 0xe4, 0x5b, 0x9d, 0x0b, 0xf6, + 0x29, 0x8c, 0xb9, 0xd4, 0xde, 0xd8, 0x27, 0xe3, 0x88, 0x4b, 0x4d, 0x26, 0x06, 0x7d, 0x2b, 0xff, + 0x2a, 0xe2, 0xc1, 0x79, 0xe7, 0xa2, 0x97, 0x90, 0xcc, 0xbe, 0x80, 0x89, 0x13, 0x65, 0x55, 0x70, + 0x27, 0x30, 0x84, 0xa1, 0x0f, 0xb4, 0x86, 0x16, 0x39, 0x1e, 0x27, 0x4b, 0xbe, 0x12, 0x69, 0xc5, + 0xdd, 0x3a, 0x5c, 0x24, 0x22, 0xe4, 0x86, 0xbb, 0x35, 0x9a, 0xad, 0xd3, 0x06, 0x1d, 0x64, 0x1e, + 0x8f, 0xbd, 0x39, 0x20, 0x8b, 0x9c, 0xfd, 0x04, 0xa2, 0x52, 0xae, 0x0c, 0x77, 0x52, 0xad, 0xe2, + 0xe8, 0xbc, 0x73, 0x31, 0x4e, 0x0e, 0x00, 0x7b, 0x01, 0x4f, 0x1d, 0x37, 0x2b, 0xe1, 0xd2, 0xd6, + 0x1e, 0x40, 0x7b, 0x1c, 0x7b, 0xc3, 0x6d, 0xb3, 0x13, 0x83, 0x3e, 0x45, 0x30, 0xf1, 0xb5, 0x46, + 0x19, 0x53, 0xb4, 0xd4, 0xa6, 0xe4, 0x2e, 0x3e, 0xf2, 0x29, 0xf2, 0x1a, 0x3b, 0x85, 0x81, 0x54, + 0xb9, 0xf8, 0x3e, 0x7e, 0x72, 0xde, 0xb9, 0x18, 0x24, 0x5e, 0x61, 0x3f, 0x83, 0x69, 0x29, 0xcc, + 0x4a, 0xa4, 0x56, 0xf1, 0xca, 0xae, 0xb5, 0x8b, 0xa7, 0x14, 0xd0, 0x13, 0x42, 0x6f, 0x03, 0xc8, + 0xa6, 0xd0, 0x5d, 0xda, 0xf8, 0x98, 0x36, 0xec, 0x2e, 0xa9, 0x92, 0xa5, 0xde, 0x28, 0x57, 0x69, + 0xa9, 0x5c, 0x7c, 0xe2, 0x13, 0x74, 0x40, 0xd8, 0x09, 0xf4, 0x72, 0xb1, 0x8d, 0x9f, 0x92, 0x01, + 0xc5, 0xf9, 0x7f, 0xfa, 0xd0, 0xfb, 0x4e, 0x66, 0x68, 0x29, 0x79, 0x16, 0xca, 0x8a, 0x22, 0xee, + 0x2d, 0xab, 0x50, 0xcf, 0xae, 0xac, 0xd0, 0x43, 0x09, 0x17, 0x8a, 0x88, 0x22, 0x7b, 0x06, 0x43, + 0x25, 0x1c, 0xe6, 0xc1, 0x17, 0x6f, 0xa0, 0x84, 0x5b, 0xe4, 0x2c, 0x86, 0xd1, 0x56, 0x1a, 0xb7, + 0xe1, 0x05, 0x55, 0x6f, 0x9c, 0xd4, 0x2a, 0x5a, 0x56, 0xdc, 0x89, 0x1d, 0xdf, 0x87, 0xe2, 0xd5, + 0x2a, 0x05, 0xa6, 0x6c, 0x28, 0x19, 0x8a, 0xad, 0xd9, 0x18, 0x3f, 0x9a, 0x8d, 0xe7, 0x30, 0x34, + 0x7a, 0xe3, 0x84, 0xa5, 0x12, 0x45, 0x49, 0xd0, 0x10, 0x97, 0x4b, 0x9a, 0x3a, 0x5f, 0x94, 0xa0, + 0xe1, 0x99, 0x25, 0xb7, 0x0f, 0x85, 0x50, 0x54, 0x8e, 0x41, 0x52, 0xab, 0xad, 0xa6, 0x3d, 0x7a, + 0xd4, 0xb4, 0xcf, 0x61, 0x78, 0x6f, 0x64, 0xbe, 0x12, 0x54, 0x92, 0x28, 0x09, 0x1a, 0x76, 0xff, + 0x4e, 0x1a, 0xaa, 0xfb, 0xd4, 0x1b, 0x50, 0xf5, 0xe5, 0xde, 0x16, 0x5c, 0x51, 0x1d, 0x06, 0x09, + 0xc9, 0xd8, 0x4c, 0x52, 0x39, 0x61, 0x96, 0x3c, 0x13, 0xa1, 0x10, 0x07, 0x00, 0x73, 0x7b, 0xbf, + 0xa3, 0x32, 0x0c, 0x92, 0xee, 0xfd, 0xee, 0xd0, 0x04, 0xac, 0xdd, 0x04, 0x5f, 0xc0, 0x24, 0x64, + 0x2e, 0x95, 0x95, 0x8d, 0x3f, 0x39, 0xef, 0x61, 0x39, 0x03, 0xb4, 0xa8, 0x2c, 0x3a, 0x88, 0xef, + 0x9d, 0x30, 0x4a, 0x14, 0x18, 0xd5, 0xa9, 0xaf, 0x77, 0x0d, 0x2d, 0x72, 0xf6, 0x19, 0x44, 0x4e, + 0xf0, 0x32, 0xdd, 0x49, 0xb7, 0x8e, 0x9f, 0x91, 0x79, 0x8c, 0xc0, 0x9f, 0xa4, 0xef, 0xc8, 0x92, + 0x2b, 0x2c, 0xd3, 0x73, 0x2a, 0x53, 0xd0, 0x70, 0x2a, 0x95, 0xcc, 0x52, 0xb7, 0xaf, 0x44, 0x7c, + 0xe6, 0xcb, 0xa4, 0x64, 0xf6, 0x6e, 0x5f, 0x51, 0x0a, 0x0a, 0xa9, 0x1e, 0xd2, 0x4d, 0x15, 0xc7, + 0x7e, 0x0d, 0xaa, 0x77, 0xd4, 0x1c, 0xa5, 0xdb, 0xc4, 0x9f, 0xd2, 0xb4, 0xa2, 0xd8, 0x70, 0xe0, + 0xec, 0xc0, 0x81, 0xf3, 0x1d, 0x4c, 0xfe, 0x78, 0x7d, 0xfd, 0xf6, 0xb5, 0x56, 0x0b, 0xb5, 0xd4, + 0xe8, 0xb2, 0xd6, 0xd6, 0xd5, 0x34, 0x89, 0x32, 0x8d, 0x8e, 0x36, 0x8e, 0xfa, 0x6e, 0x90, 0x90, + 0x4c, 0xd4, 0x69, 0x85, 0x09, 0xad, 0x47, 0x32, 0x06, 0x5f, 0x71, 0x6b, 0x77, 0x75, 0xef, 0x05, + 0x0d, 0x33, 0xb9, 0x2d, 0x8d, 0x58, 0x52, 0xeb, 0x45, 0x89, 0x57, 0xe6, 0xff, 0xec, 0x02, 0x5c, + 0x13, 0x6b, 0xd3, 0xc1, 0x2f, 0x01, 0xaa, 0xcd, 0x7d, 0x21, 0xb3, 0xf4, 0x41, 0xec, 0xe9, 0xf8, + 0xc9, 0xd5, 0x13, 0xcf, 0x8b, 0xb7, 0xb7, 0xdf, 0xbc, 0x15, 0x7b, 0x9b, 0x44, 0xde, 0xe1, 0xad, + 0xd8, 0xb3, 0x57, 0x30, 0xf2, 0x8c, 0x6f, 0xe3, 0x2e, 0x51, 0xe8, 0x27, 0x81, 0x42, 0x09, 0x7c, + 0xad, 0x95, 0x13, 0xca, 0x25, 0xb5, 0x0f, 0x9b, 0xc1, 0x98, 0x62, 0xd1, 0x26, 0x0f, 0x11, 0x37, + 0x3a, 0xe6, 0x4f, 0xda, 0x54, 0x2a, 0xe9, 0x28, 0xec, 0x71, 0x32, 0x94, 0x76, 0xa1, 0xa4, 0x43, + 0x6a, 0x12, 0x8a, 0xdf, 0x17, 0x22, 0x75, 0x6e, 0x1f, 0xc6, 0x26, 0xf2, 0xc8, 0x3b, 0xb7, 0x47, + 0xf2, 0xc9, 0xc5, 0x92, 0x6f, 0x0a, 0x97, 0x1a, 0xad, 0x5d, 0x4a, 0xe9, 0x18, 0x92, 0xd7, 0x71, + 0x30, 0x24, 0x5a, 0xbb, 0x3b, 0xcc, 0xcc, 0x6f, 0x60, 0xb6, 0x93, 0x2a, 0xd7, 0x3b, 0x9b, 0xd6, + 0x6b, 0x78, 0x5e, 0x4a, 0xe5, 0x17, 0x8d, 0x68, 0xd1, 0x59, 0xf0, 0xb8, 0xf6, 0x0e, 0x5f, 0xa1, + 0x9d, 0x16, 0xbf, 0x80, 0xa7, 0x21, 0x8e, 0xac, 0xd0, 0x9b, 0xdc, 0x87, 0x3a, 0xf6, 0x07, 0x79, + 0xc3, 0x6b, 0xc4, 0x31, 0xe6, 0xf9, 0xdf, 0x3a, 0x30, 0x0a, 0xe9, 0xc2, 0xf8, 0xdf, 0xcb, 0x68, + 0xd4, 0x4e, 0x21, 0xc5, 0x5f, 0x08, 0x27, 0xd2, 0x96, 0x97, 0xa7, 0x96, 0x63, 0x6f, 0xb8, 0x69, + 0x7c, 0x2f, 0xe0, 0xc4, 0xc7, 0xdb, 0x72, 0xf5, 0x79, 0x9c, 0x12, 0x7e, 0xf0, 0x7c, 0x09, 0xac, + 0x32, 0xfa, 0x2f, 0x22, 0x73, 0x6d, 0x5f, 0xdf, 0x0f, 0x27, 0xc1, 0xd2, 0x78, 0xcf, 0xef, 0xe0, + 0xc9, 0xa3, 0x8a, 0x35, 0x2c, 0xdd, 0x69, 0xb1, 0x74, 0x0c, 0xa3, 0xcc, 0x9b, 0x43, 0x78, 0xb5, + 0x8a, 0x0d, 0xc7, 0x33, 0x27, 0x75, 0xf3, 0x56, 0x7b, 0x6d, 0x3e, 0x82, 0xc1, 0xef, 0xcb, 0xca, + 0xed, 0xe7, 0x7f, 0xef, 0xc0, 0x33, 0x7f, 0x00, 0x7d, 0x08, 0xbc, 0xb1, 0x89, 0xb0, 0x95, 0x56, + 0x56, 0x10, 0xd1, 0x48, 0xeb, 0x8c, 0x6e, 0xbd, 0x9a, 0xce, 0x68, 0x22, 0x4a, 0x61, 0x2c, 0xee, + 0x19, 0x0e, 0x0b, 0x2a, 0x86, 0xc6, 0x4d, 0xb6, 0xae, 0x3b, 0x1e, 0x65, 0xec, 0xab, 0x82, 0xab, + 0xd5, 0x86, 0xaf, 0xea, 0xc7, 0xb2, 0xd1, 0x91, 0x4f, 0xb4, 0x0d, 0x2d, 0xdf, 0xd5, 0x16, 0x77, + 0xe6, 0x59, 0x86, 0xbc, 0x5f, 0x13, 0x6d, 0x50, 0x71, 0x50, 0x31, 0x49, 0x81, 0x68, 0x1f, 0xc4, + 0x7e, 0xfe, 0x8f, 0x0e, 0x1c, 0xf9, 0xb8, 0x6f, 0xb8, 0xe1, 0xa5, 0x45, 0xd2, 0xa0, 0x57, 0xbe, + 0x95, 0x9c, 0x31, 0x02, 0xf4, 0x86, 0x5e, 0x02, 0xac, 0xf0, 0x7a, 0x69, 0x2e, 0x6c, 0x46, 0x61, + 0x4f, 0xae, 0x8e, 0xfd, 0x3c, 0x34, 0xdf, 0x3f, 0x49, 0xb4, 0x6a, 0x3e, 0x85, 0x7e, 0x01, 0x13, + 0x3f, 0x18, 0xa9, 0x54, 0x4b, 0x4d, 0x17, 0x9a, 0x5c, 0x9d, 0xb4, 0x07, 0x08, 0x27, 0x32, 0x81, + 0xfc, 0x30, 0x9d, 0x97, 0x10, 0x6d, 0xf3, 0xfc, 0xc1, 0x2f, 0xe8, 0xd3, 0x82, 0xa7, 0x7e, 0x41, + 0x8b, 0x3c, 0x92, 0x31, 0xfa, 0xa0, 0x34, 0x7f, 0x05, 0xd3, 0x44, 0xe0, 0x07, 0xc2, 0x1b, 0xfb, + 0x11, 0x37, 0x98, 0xff, 0x19, 0xa6, 0x6f, 0xe8, 0xe9, 0xfd, 0x28, 0x77, 0x34, 0x2e, 0x6d, 0x1a, + 0x9e, 0x6e, 0x5f, 0xa6, 0xf1, 0xd2, 0xfa, 0x1d, 0x9a, 0x8f, 0xba, 0xde, 0xe1, 0xa3, 0x6e, 0xae, + 0x61, 0x92, 0x88, 0x42, 0x70, 0x2b, 0xe8, 0x36, 0xff, 0xf7, 0xe2, 0xcf, 0xbf, 0x05, 0x76, 0xcb, + 0xb7, 0xe2, 0x9d, 0xfe, 0xba, 0xe0, 0x2a, 0x13, 0x1f, 0x73, 0xa9, 0x19, 0x8c, 0x33, 0x5d, 0x56, + 0x46, 0x58, 0x4b, 0xa7, 0x8f, 0x93, 0x46, 0x9f, 0x0b, 0x38, 0x6d, 0x6f, 0xd7, 0x74, 0xf1, 0x19, + 0x8c, 0xb4, 0xf5, 0x45, 0x09, 0x37, 0xd1, 0x96, 0x6e, 0xf8, 0x4b, 0x38, 0x32, 0xfe, 0xc2, 0xde, + 0xda, 0x6d, 0x97, 0xac, 0x95, 0x8a, 0x64, 0x62, 0x0e, 0xca, 0xfc, 0x4b, 0x38, 0xbd, 0x31, 0xfa, + 0x5e, 0x2c, 0xf0, 0xf3, 0x0c, 0x91, 0x1b, 0xc3, 0x4b, 0xfe, 0x3f, 0x6a, 0xf7, 0x43, 0x17, 0xa2, + 0x66, 0x01, 0x7b, 0xf1, 0x38, 0xa2, 0x0f, 0x9e, 0x59, 0x07, 0xe9, 0xa3, 0xa7, 0x37, 0xad, 0x5b, + 0x47, 0x4f, 0x4f, 0xda, 0xcf, 0xe1, 0x58, 0xda, 0x74, 0x23, 0x96, 0x32, 0xb5, 0x9b, 0x8a, 0xde, + 0x9e, 0x9e, 0xff, 0xd4, 0x92, 0xf6, 0x4e, 0x2c, 0xe5, 0xad, 0x07, 0x91, 0x96, 0xa4, 0x4d, 0x8b, + 0x6d, 0x99, 0x56, 0xdc, 0x38, 0x49, 0x4c, 0xe0, 0x39, 0x7c, 0x2a, 0xed, 0x1f, 0xb6, 0xe5, 0x4d, + 0x8d, 0xe2, 0xab, 0x2c, 0x6d, 0x6a, 0x04, 0xcf, 0xb5, 0x2a, 0x6a, 0x32, 0x07, 0x69, 0x93, 0x80, + 0xb0, 0x5f, 0xc3, 0x59, 0xb5, 0xde, 0x5b, 0x99, 0xf1, 0xe2, 0xb0, 0x99, 0x8f, 0xcd, 0x4f, 0xeb, + 0xb3, 0xda, 0xdc, 0x6c, 0x4a, 0xa1, 0xfe, 0x0a, 0xce, 0xe8, 0xf5, 0xb0, 0x8e, 0x17, 0x85, 0xc8, + 0xdb, 0x14, 0xed, 0x69, 0xfd, 0x14, 0x5f, 0x93, 0x60, 0x6d, 0x78, 0xfa, 0xea, 0x87, 0x2e, 0x4c, + 0xfc, 0xa8, 0x7d, 0xb5, 0x42, 0x26, 0xfb, 0x5d, 0x4d, 0x84, 0x81, 0xa7, 0x18, 0x6b, 0x8f, 0xa3, + 0x6f, 0x9f, 0xd9, 0x67, 0x6d, 0xec, 0x7d, 0x42, 0x7b, 0x05, 0xe3, 0x7a, 0xe2, 0xd8, 0x69, 0x9d, + 0xf3, 0xf6, 0x04, 0xce, 0x26, 0x1e, 0x25, 0x66, 0x44, 0xf7, 0x7a, 0xe2, 0x6a, 0xf7, 0xc7, 0x13, + 0xf8, 0xd8, 0xfd, 0x1a, 0x8e, 0xda, 0x0d, 0xc8, 0xe2, 0xf0, 0x32, 0xff, 0xa8, 0xc7, 0x67, 0xb3, + 0x1f, 0x5b, 0x9a, 0x18, 0x7f, 0x0b, 0xd3, 0xc7, 0xfd, 0xc5, 0x82, 0xf7, 0x87, 0xba, 0x6e, 0x16, + 0x28, 0xac, 0x81, 0xef, 0x87, 0xf4, 0xef, 0xee, 0xcb, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xb9, + 0xf7, 0xec, 0x82, 0xed, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/hostman/hostdeployer/apis/deploy.proto b/pkg/hostman/hostdeployer/apis/deploy.proto index d8b2e671a9..6fa1720fa4 100644 --- a/pkg/hostman/hostdeployer/apis/deploy.proto +++ b/pkg/hostman/hostdeployer/apis/deploy.proto @@ -10,6 +10,8 @@ message GuestDesc { repeated Nic nics = 4; repeated Nic nics_standby = 5; repeated Disk disks = 6; + + string Hypervisor = 7; } message Disk { @@ -32,7 +34,6 @@ message Disk { string dev = 17; } - message Nic { string mac = 1; string ip = 2; @@ -62,6 +63,14 @@ message Nic { string name = 26; } +message VDDKConInfo { + string host = 1; + int32 port = 2; + string user = 3; + string passwd = 4; + string vmref = 5; +} + message DeployInfo { SSHKeys public_key = 1; repeated DeployContent deploys = 2; @@ -103,6 +112,7 @@ message DeployParams { string disk_path = 1; GuestDesc guest_desc = 2; DeployInfo deploy_info = 3; + VDDKConInfo vddk_info = 4; } message ResizeFsParams { diff --git a/pkg/hostman/hostdeployer/deployserver/deployserver.go b/pkg/hostman/hostdeployer/deployserver/deployserver.go index 443ad9bfdf..1f297fc660 100644 --- a/pkg/hostman/hostdeployer/deployserver/deployserver.go +++ b/pkg/hostman/hostdeployer/deployserver/deployserver.go @@ -16,17 +16,19 @@ package deployserver import ( "context" - "errors" "fmt" "net" "os" + "runtime/debug" "strings" "google.golang.org/grpc" execlient "yunion.io/x/executor/client" "yunion.io/x/log" + "yunion.io/x/pkg/errors" + comapi "yunion.io/x/onecloud/pkg/apis/compute" common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" "yunion.io/x/onecloud/pkg/cloudcommon/service" "yunion.io/x/onecloud/pkg/hostman/diskutils" @@ -43,21 +45,35 @@ import ( type DeployerServer struct{} func (*DeployerServer) DeployGuestFs(ctx context.Context, req *deployapi.DeployParams, -) (*deployapi.DeployGuestFsResponse, error) { +) (res *deployapi.DeployGuestFsResponse, err error) { + // There will be some occasional unknown panic, so temporarily capture panic here. + defer func() { + if r := recover(); r != nil { + log.Errorf("DeployGuestFs: %s", r) + debug.PrintStack() + msg := "panic: " + if str, ok := r.(fmt.Stringer); ok { + msg += str.String() + } + res, err = nil, errors.Error(msg) + } + }() log.Infof("Deploy guest fs on %s", req.DiskPath) - var kvmDisk = diskutils.NewKVMGuestDisk(req.DiskPath) - defer kvmDisk.Disconnect() - if !kvmDisk.Connect() { - log.Infof("Failed to connect kvm disk") - return new(deployapi.DeployGuestFsResponse), nil + var disk = diskutils.GetIDisk(req) + if len(req.GuestDesc.Hypervisor) == 0 { + req.GuestDesc.Hypervisor = comapi.HYPERVISOR_KVM } - - root := kvmDisk.MountKvmRootfs() + defer disk.Disconnect() + if !disk.Connect() { + log.Infof("Failed to connect %s disk", req.GuestDesc.Hypervisor) + return new(deployapi.DeployGuestFsResponse), errors.Error("disk connect failed") + } + root := disk.MountRootfs() if root == nil { - log.Infof("Failed mounting rootfs for kvm disk") - return new(deployapi.DeployGuestFsResponse), nil + log.Infof("Failed mounting rootfs for %s disk", req.GuestDesc.Hypervisor) + return new(deployapi.DeployGuestFsResponse), errors.Error("rootfs mount failed") } - defer kvmDisk.UmountKvmRootfs(root) + defer disk.UmountRootfs(root) ret, err := guestfs.DoDeployGuestFs(root, req.GuestDesc, req.DeployInfo) if err != nil { @@ -75,7 +91,7 @@ func (*DeployerServer) ResizeFs(ctx context.Context, req *deployapi.ResizeFsPara disk := diskutils.NewKVMGuestDisk(req.DiskPath) defer disk.Disconnect() if !disk.Connect() { - return new(deployapi.Empty), errors.New("resize fs disk connect failed") + return new(deployapi.Empty), errors.Error("resize fs disk connect failed") } root := disk.MountKvmRootfs() @@ -166,7 +182,7 @@ func (*DeployerServer) ProbeImageInfo(ctx context.Context, req *deployapi.ProbeI defer kvmDisk.Disconnect() if !kvmDisk.Connect() { log.Infof("Failed to connect kvm disk") - return new(deployapi.ImageInfo), errors.New("Disk connector failed to connect image") + return new(deployapi.ImageInfo), errors.Error("Disk connector failed to connect image") } // Fsck is executed during mount diff --git a/pkg/hostman/storageman/disk_agent.go b/pkg/hostman/storageman/disk_agent.go new file mode 100644 index 0000000000..fcb189fcdc --- /dev/null +++ b/pkg/hostman/storageman/disk_agent.go @@ -0,0 +1,117 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storageman + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/multicloud/esxi" +) + +type SAgentDisk struct { + SLocalDisk +} + +func NewAgentDisk(storage IStorage, id string) *SAgentDisk { + return &SAgentDisk{*NewLocalDisk(storage, id)} +} + +type PrepareSaveToGlanceParams struct { + TaskId string + DiskInfo jsonutils.JSONObject +} + +func (sd *SAgentDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + p, ok := params.(PrepareSaveToGlanceParams) + if !ok { + return nil, errors.Wrap(hostutils.ParamsError, "Resize params format error") + } + storage := sd.Storage.(*SAgentStorage) + return storage.PrepareSaveToGlance(ctx, p.TaskId, p.DiskInfo) +} + +func (sd *SAgentDisk) ReSize(ctx context.Context, diskInfo interface{}) (jsonutils.JSONObject, error) { + body, ok := diskInfo.(*jsonutils.JSONDict) + if !ok { + return nil, errors.Wrap(hostutils.ParamsError, "PrepareSaveToGlance params format error") + } + // check parameters + params := []string{"size", "host_info", "vm_private_id", "disk_private_id"} + for _, param := range params { + if !body.Contains(param) { + return nil, httperrors.NewMissingParameterError(param) + } + } + + type sResize struct { + SizeMb int64 `json:"size"` + HostInfo models.SVCenterAccessInfo + VMId string `json:"vm_private_id"` + DiskId string `json:"disk_private_id"` + } + resize := sResize{} + err := body.Unmarshal(&resize) + if err != nil { + return nil, errors.Wrapf(err, "%s: unmarshal to sResize", hostutils.ParamsError) + } + + esxiClient, err := esxi.NewESXiClientFromAccessInfo(ctx, &resize.HostInfo) + if err != nil { + return nil, httperrors.NewInputParameterError("info of host_info error") + } + host, err := esxiClient.FindHostByIp(resize.HostInfo.PrivateId) + if err != nil { + return nil, errors.Wrapf(err, "fail to find host by ip %s", resize.HostInfo.PrivateId) + } + ivm, err := host.GetIVMById(resize.VMId) + if err != nil { + return nil, errors.Wrapf(err, "fail to find vm by ID %s", resize.VMId) + } + idisks, err := ivm.GetIDisks() + if err != nil { + return nil, errors.Wrapf(err, "fail to get idisks of vm %s", resize.VMId) + } + var ( + idisk cloudprovider.ICloudDisk + hasDisk bool + ) + for i := range idisks { + if idisks[i].GetId() == resize.DiskId { + idisk = idisks[i] + hasDisk = true + } + } + if !hasDisk { + return nil, errors.Wrapf(err, "no such disk %s", resize.DiskId) + } + url := idisk.GetAccessPath() + vm, disk := ivm.(*esxi.SVirtualMachine), idisk.(*esxi.SVirtualDisk) + online := jsonutils.QueryBoolean(body, "online", false) + if online { + esxiClient.DoExtendDiskOnline(vm, disk, resize.SizeMb) + } else { + esxiClient.ExtendDisk(url, resize.SizeMb) + } + desc := jsonutils.NewDict() + desc.Add(jsonutils.NewInt(resize.SizeMb), "disk_size") + return desc, nil +} diff --git a/pkg/hostman/storageman/disk_rbd.go b/pkg/hostman/storageman/disk_rbd.go index 88b55fe4d4..68584dd99c 100644 --- a/pkg/hostman/storageman/disk_rbd.go +++ b/pkg/hostman/storageman/disk_rbd.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux +// +build linux,cgo package storageman diff --git a/pkg/hostman/storageman/imagecache_rbd.go b/pkg/hostman/storageman/imagecache_rbd.go index b5bcc336ce..1dbc9b5e6e 100644 --- a/pkg/hostman/storageman/imagecache_rbd.go +++ b/pkg/hostman/storageman/imagecache_rbd.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux +// +build linux,cgo package storageman diff --git a/pkg/hostman/storageman/imagecachemanager_agent.go b/pkg/hostman/storageman/imagecachemanager_agent.go index 6ed518f60f..91c679d00d 100644 --- a/pkg/hostman/storageman/imagecachemanager_agent.go +++ b/pkg/hostman/storageman/imagecachemanager_agent.go @@ -14,11 +14,217 @@ package storageman -// not need to impl, using esxi agent +import ( + "context" + "fmt" + "path/filepath" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/hostman/hostutils" + "yunion.io/x/onecloud/pkg/multicloud/esxi" +) + type SAgentImageCacheManager struct { - storagemanager *SStorageManager + imageCacheManger IImageCacheManger } -func NewAgentImageCacheManager(storagemanager *SStorageManager) *SAgentImageCacheManager { - return &SAgentImageCacheManager{storagemanager} +func NewAgentImageCacheManager(manger IImageCacheManger) *SAgentImageCacheManager { + return &SAgentImageCacheManager{manger} +} + +type sImageCacheData struct { + ImageId string + HostId string + HostIp string + SrcHostIp string + SrcPath string + SrcDatastore models.SVCenterAccessInfo + Datastore models.SVCenterAccessInfo + Format string + IsForce bool + StoragecacheId string +} + +func (c *SAgentImageCacheManager) PrefetchImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) { + dataDict, ok := data.(*jsonutils.JSONDict) + if !ok { + return nil, errors.Wrap(hostutils.ParamsError, "PrefetchImageCache data format error") + } + idata := new(sImageCacheData) + err := dataDict.Unmarshal(idata) + if err != nil { + return nil, errors.Wrap(err, "%s: unmarshal to sImageCacheData error") + } + lockman.LockRawObject(ctx, idata.HostId, idata.ImageId) + defer lockman.ReleaseRawObject(ctx, idata.HostId, idata.ImageId) + if len(idata.SrcHostIp) != 0 { + return c.prefetchImageCacheByCopy(ctx, idata) + } + return c.prefetchImageCacheByUpload(ctx, idata, dataDict) +} + +func (c *SAgentImageCacheManager) prefetchImageCacheByCopy(ctx context.Context, data *sImageCacheData) (jsonutils.JSONObject, error) { + client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore) + if err != nil { + return nil, err + } + dstHost, err := client.FindHostByIp(data.HostIp) + if err != nil { + return nil, err + } + dstDs, err := dstHost.FindDataStoreById(data.Datastore.PrivateId) + if err != nil { + return nil, err + } + srcHost, err := client.FindHostByIp(data.SrcHostIp) + if err != nil { + return nil, err + } + srcDs, err := srcHost.FindDataStoreById(data.SrcDatastore.PrivateId) + if err != nil { + return nil, err + } + srcPath := data.SrcPath[len(srcDs.GetUrl()):] + dstPath := fmt.Sprintf("image_cache/%s.vmdk", data.ImageId) + + // check if dst vmdk has been existed + exists := false + log.Infof("check file: src=%s, dst=%s", srcPath, dstPath) + dstVmdkInfo, err := dstDs.GetVmdkInfo(ctx, dstPath) + if err != nil { + return nil, err + } + srcVmdkInfo, err := srcDs.GetVmdkInfo(ctx, srcPath) + if err != nil { + return nil, err + } + if dstVmdkInfo == srcVmdkInfo { + exists = true + } + + dstUrl := dstDs.GetPathUrl(dstPath) + if !exists || data.IsForce { + _, err = dstDs.MakeDir(ctx, dstPath) + if err != nil { + return nil, errors.Wrap(err, "dstDs.MakeDir") + } + srcUrl := srcDs.GetPathUrl(srcPath) + log.Infof("Copy %s => %s", srcUrl, dstUrl) + err = client.CopyDisk(ctx, srcUrl, dstUrl, data.IsForce) + if err != nil { + return nil, errors.Wrap(err, "client.CopyDisk") + } + dstVmdkInfo, err = dstDs.GetVmdkInfo(ctx, dstPath) + if err != nil { + return nil, errors.Wrap(err, "dstDs.GetVmdkInfo") + } + } + ret := jsonutils.NewDict() + ret.Add(jsonutils.NewInt(dstVmdkInfo.Size()), "size") + ret.Add(jsonutils.NewString(dstUrl), "path") + ret.Add(jsonutils.NewString(data.ImageId), "image_id") + _, err = hostutils.RemoteStoragecacheCacheImage(ctx, data.StoragecacheId, data.ImageId, "ready", dstUrl) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (c *SAgentImageCacheManager) prefetchImageCacheByUpload(ctx context.Context, data *sImageCacheData, + origin *jsonutils.JSONDict) (jsonutils.JSONObject, error) { + + format := "vmdk" + localImage, err := c.imageCacheManger.PrefetchImageCache(ctx, origin) + if err != nil { + return nil, err + } + localImgPath, _ := localImage.GetString("path") + localImgSize, _ := localImage.Int("size") + + client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore) + if err != nil { + return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson") + } + host, err := client.FindHostByIp(data.HostIp) + if err != nil { + return nil, err + } + ds, err := host.FindDataStoreById(data.Datastore.PrivateId) + if err != nil { + return nil, errors.Wrap(err, "SHost.FindDataStoreById") + } + remotePath := fmt.Sprintf("image_cache/%s.%s", data.ImageId, format) + + // check if dst vmdk is exist + exists := false + if format == "vmdk" { + err = ds.CheckVmdk(ctx, remotePath) + if err != nil { + log.Debugf("ds.CheckVmdk failed: %s", err) + } else { + exists = true + } + } else { + ret, err := ds.CheckFile(ctx, remotePath) + if err != nil { + log.Debugf("ds.CheckFile failed: %s", err) + } else { + if int64(ret.Size) == localImgSize { + // exist and same size + exists = true + } + } + } + log.Debugf("exist: %t, remotePath: %s", exists, remotePath) + if !exists || data.IsForce { + err := ds.ImportVMDK(ctx, localImgPath, remotePath, host) + //err := ds.ImportVMDK(ctx, localImgPath, host) + if err != nil { + return nil, errors.Wrap(err, "SDatastore.ImportTemplate") + } + } + remotePath = filepath.Join(ds.GetUrl(), remotePath) + remoteImg := localImage.(*jsonutils.JSONDict) + remoteImg.Add(jsonutils.NewString(remotePath), "path") + + _, err = hostutils.RemoteStoragecacheCacheImage(ctx, data.StoragecacheId, data.ImageId, "ready", remotePath) + if err != nil { + return nil, err + } + log.Debugf("prefetchImageCacheByUpload over") + return remoteImg, nil +} + +func (c *SAgentImageCacheManager) DeleteImageCache(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) { + dataDict, ok := data.(*jsonutils.JSONDict) + if !ok { + return nil, errors.Wrap(hostutils.ParamsError, "DeleteImageCache data format error") + } + var ( + imageID, _ = dataDict.GetString("image_id") + hostIP, _ = dataDict.GetString("host_ip") + dsInfo, _ = dataDict.Get("ds_info") + ) + + client, _, err := esxi.NewESXiClientFromJson(ctx, dsInfo) + if err != nil { + return nil, err + } + host, err := client.FindHostByIp(hostIP) + if err != nil { + return nil, err + } + dsID, _ := dsInfo.GetString("private_id") + ds, err := host.FindDataStoreById(dsID) + if err != nil { + return nil, err + } + remotePath := fmt.Sprintf("image_cache/%s.vmdk", imageID) + return nil, ds.Delete(ctx, remotePath) } diff --git a/pkg/hostman/storageman/imagecachemanager_local.go b/pkg/hostman/storageman/imagecachemanager_local.go index 4e310e4ae6..9a044d8e25 100644 --- a/pkg/hostman/storageman/imagecachemanager_local.go +++ b/pkg/hostman/storageman/imagecachemanager_local.go @@ -162,5 +162,3 @@ func (c *SLocalImageCacheManager) PrefetchImageCache(ctx context.Context, data i return nil, fmt.Errorf("Failed to fetch image %s", imageId) } } - -// TODO: AgentImageCacheManager diff --git a/pkg/hostman/storageman/imagecachemanager_rbd.go b/pkg/hostman/storageman/imagecachemanager_rbd.go index 288f2c368c..918673f77f 100644 --- a/pkg/hostman/storageman/imagecachemanager_rbd.go +++ b/pkg/hostman/storageman/imagecachemanager_rbd.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux +// +build linux,cgo package storageman diff --git a/pkg/hostman/storageman/storage_agent.go b/pkg/hostman/storageman/storage_agent.go new file mode 100644 index 0000000000..612d910b78 --- /dev/null +++ b/pkg/hostman/storageman/storage_agent.go @@ -0,0 +1,564 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storageman + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/seclib" + "yunion.io/x/pkg/util/timeutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/agent/iagent" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" + "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient" + "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/multicloud/esxi" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemuimg" +) + +type SAgentStorage struct { + SLocalStorage + agent iagent.IAgent +} + +func NewAgentStorage(manager *SStorageManager, agent iagent.IAgent, path string) *SAgentStorage { + s := &SAgentStorage{SLocalStorage: *NewLocalStorage(manager, path, 0)} + s.agent = agent + s.checkDirC(path) + return s +} + +func (as *SAgentStorage) GetDiskById(diskId string) IDisk { + return NewAgentDisk(as, diskId) +} + +func (as *SAgentStorage) CreateDiskByDiskInfo(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + createParams, ok := params.(*SDiskCreateByDiskinfo) + if !ok { + return nil, errors.Wrap(hostutils.ParamsError, "CreateDiskByDiskInfo params format error") + } + + hd := SHostDatastore{} + err := createParams.DiskInfo.Unmarshal(&hd) + if err != nil { + return nil, errors.Wrap(hostutils.ParamsError, err.Error()) + } + + diskMeta, err := as.SLocalStorage.CreateDiskByDiskinfo(ctx, params) + if err != nil { + return nil, errors.Wrap(err, "as.SLocalStorage.CreateDiskByDiskinfo") + } + disk := as.GetDiskById(createParams.DiskId) + + _, ds, err := as.getHostAndDatastore(ctx, hd) + if err != nil { + return nil, errors.Wrap(err, "as.getHostAndDatastore") + } + remotePath := "disks/" + createParams.DiskId + file, err := os.Open(disk.GetPath()) + if err != nil { + return nil, errors.Wrap(err, "fail to open disk path") + } + defer file.Close() + err = ds.Upload(ctx, remotePath, file) + if err != nil { + return nil, errors.Wrap(err, "dataStore.Upload") + } + as.RemoveDisk(disk) + return diskMeta, nil +} + +func (as *SAgentStorage) agentRebuildRoot(ctx context.Context, data jsonutils.JSONObject) error { + type sRebuildParam struct { + GuestExtId string + SHostDatastore + Desc struct { + Disks []struct { + DiskId string + ImagePath string + } + } + } + rp := sRebuildParam{} + err := data.Unmarshal(&rp) + if err != nil { + return errors.Wrap(err, hostutils.ParamsError.Error()) + } + host, _, err := as.getHostAndDatastore(ctx, rp.SHostDatastore) + if err != nil { + return errors.Wrap(err, "as.getHostAndDatastore") + } + ivm, err := host.GetIVMById(rp.GuestExtId) + if err != nil { + return errors.Wrapf(err, "host.GetIVMById of id '%s'", rp.GuestExtId) + } + if len(rp.Desc.Disks) == 0 { + return errors.Wrap(hostutils.ParamsError, "agentRebuildRoot data.desc.disks is empty") + } + imagePath := rp.Desc.Disks[0].ImagePath + diskId := rp.Desc.Disks[0].DiskId + newPath, err := host.FileUrlPathToDsPath(imagePath) + if err != nil { + return err + } + vm := ivm.(*esxi.SVirtualMachine) + return vm.DoRebuildRoot(ctx, newPath, diskId) +} + +func (as *SAgentStorage) agentCreateGuest(ctx context.Context, data *jsonutils.JSONDict) error { + hd := SHostDatastore{} + err := data.Unmarshal(&hd) + if err != nil { + return errors.Wrap(err, hostutils.ParamsError.Error()) + } + host, ds, err := as.getHostAndDatastore(ctx, hd) + if err != nil { + return err + } + desc, _ := data.Get("desc") + descDict, ok := desc.(*jsonutils.JSONDict) + if !ok { + return errors.Wrap(hostutils.ParamsError, "agentCreateGuest data format error") + } + vm, err := host.DoCreateVM(ctx, ds, descDict) + if err != nil { + return errors.Wrap(err, "SHost.DoCreateVM") + } + /* + id, _ := data.GetString("guest_ext_id") + ivm, err := host.GetIVMById(id) + if err != nil { + return errors.Wrap(err, "SHost.GetIVMById") + } + */ + name, _ := descDict.GetString("name") + err = as.tryRenameVm(ctx, vm, name) + if err != nil { + return errors.Wrapf(err, "RenameVm name '%s'", name) + } + return nil +} + +func (as *SAgentStorage) tryRenameVm(ctx context.Context, vm *esxi.SVirtualMachine, name string) error { + var ( + tried = 0 + err error + ) + alterName := fmt.Sprintf("%s-%s", name, timeutils.ShortDate(time.Now())) + cands := []string{name, alterName} + + for tried < 10 { + var n string + if tried < len(cands) { + n = cands[tried] + } else { + n = fmt.Sprintf("%s-%d", alterName, tried-len(cands)+1) + } + tried += 1 + err = vm.DoRename(ctx, n) + if err == nil { + return nil + } + } + return err +} + +func (as *SAgentStorage) AgentDeployGuest(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) { + init := false + dataDict := data.(*jsonutils.JSONDict) + action, _ := dataDict.GetString("action") + if action == "create" { + err := as.agentCreateGuest(ctx, dataDict) + if err != nil { + return nil, errors.Wrap(err, "agentCreateGuest") + } + init = true + } else if action == "rebuild" { + err := as.agentRebuildRoot(ctx, dataDict) + if err != nil { + return nil, errors.Wrap(err, "agentRebuildRoot") + } + } + + var ( + hostIp, _ = dataDict.GetString("host_ip") + dsInfo, _ = dataDict.Get("datastore") + ) + dc, info, err := esxi.NewESXiClientFromJson(ctx, dsInfo) + if err != nil { + return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson") + } + host, err := dc.FindHostByIp(hostIp) + if err != nil { + return nil, errors.Wrap(err, "SDatacenter.FindHostByIp") + } + vmId, _ := dataDict.GetString("guest_ext_id") + realHost := host + ivm, err := host.GetIVMById(vmId) + if err == cloudprovider.ErrNotFound { + // reschedule by DRS, migrate to other host + siblingHosts, err := host.GetSiblingHosts() + if err != nil { + return nil, errors.Wrap(err, "SHost.GetSiblingHosts") + } + for _, sh := range siblingHosts { + ivm, err = host.GetIVMById(vmId) + if err == nil { + realHost = sh + break + } + } + } + if err != nil { + return nil, errors.Wrap(err, "SHost.GetIVMById") + } + if ivm == nil { + return nil, errors.Error(fmt.Sprintf("no such vm '%s'", ivm.GetId())) + } + vm := ivm.(*esxi.SVirtualMachine) + disks, err := vm.GetIDisks() + if err != nil { + return nil, errors.Wrap(err, "VM.GetIDisks") + } + if len(disks) == 0 { + return nil, errors.Error(fmt.Sprintf("no such disks for vm %s", vm.GetId())) + } + vmref := vm.GetMoid() + rootPath := disks[0].(*esxi.SVirtualDisk).GetFilename() + + key := deployapi.SSHKeys{} + err = dataDict.Unmarshal(&key, "desc") + if err != nil { + return nil, errors.Wrapf(err, "%s: unmarshal to deployapi.SSHKeys", hostutils.ParamsError.Error()) + } + + deployArray := make([]*deployapi.DeployContent, 0) + if dataDict.Contains("deploys") { + err = dataDict.Unmarshal(&deployArray, "deploys") + if err != nil { + return nil, errors.Wrapf(err, "%s: unmarshal to array of deployapi.DeployContent", hostutils.ParamsError.Error()) + } + } + + resetPassword := jsonutils.QueryBoolean(dataDict, "reset_password", false) + passwd, _ := dataDict.GetString("password") + if resetPassword && len(passwd) == 0 { + passwd = seclib.RandomPassword(12) + } + + log.Debugf("host: %s, port: %d, user: %s, passwd: %s", info.Host, info.Port, info.Account, info.Password) + vddkInfo := deployapi.VDDKConInfo{ + Host: info.Host, + Port: int32(info.Port), + User: info.Account, + Passwd: info.Password, + Vmref: vmref, + } + guestDesc := deployapi.GuestDesc{} + err = dataDict.Unmarshal(&guestDesc, "desc") + if err != nil { + return nil, errors.Wrapf(err, "%s: unmarshal to guestDesc", hostutils.ParamsError.Error()) + } + + desc, _ := dataDict.Get("desc") + guestDesc.Hypervisor = api.HYPERVISOR_ESXI + deploy, err := deployclient.GetDeployClient().DeployGuestFs(ctx, &deployapi.DeployParams{ + DiskPath: rootPath, + GuestDesc: &guestDesc, + DeployInfo: &deployapi.DeployInfo{ + PublicKey: &key, + Deploys: deployArray, + Password: passwd, + IsInit: init, + }, + VddkInfo: &vddkInfo, + }) + if err != nil { + log.Errorf("DeployClient.DeployGuestFs: %s", err) + // if deploy fail, try customization + as.waitVmToolsVersion(ctx, vm) + err = vm.DoCustomize(ctx, desc) + if err != nil { + return nil, errors.Wrap(err, "VM.DoCustomize") + } + } + + array := jsonutils.NewArray() + diskArray, _ := desc.GetArray("disks") + for idx, d := range disks { + disk := d.(*esxi.SVirtualDisk) + diskId, _ := diskArray[idx].GetString("disk_id") + diskDict := jsonutils.NewDict() + diskDict.Add(jsonutils.NewString(diskId), "disk_id") + diskDict.Add(jsonutils.NewString(disk.GetGlobalId()), "uuid") + diskDict.Add(jsonutils.NewInt(int64(disk.GetDiskSizeMB())), "size") + diskDict.Add(jsonutils.NewString(disk.GetFilename()), "path") + diskDict.Add(jsonutils.NewString(disk.GetCacheMode()), "cache_mode") + diskDict.Add(jsonutils.NewString(disk.GetDiskType()), "disk_type") + diskDict.Add(jsonutils.NewString(disk.GetDriver()), "driver") + array.Add(diskDict) + } + updated := jsonutils.NewDict() + updated.Add(array, "disks") + updated.Add(jsonutils.NewString(vm.GetGlobalId()), "uuid") + updated.Add(jsonutils.NewString(realHost.GetAccessIp()), "host_ip") + ret := jsonutils.Marshal(deploy) + ret.(*jsonutils.JSONDict).Update(updated) + return ret, nil +} + +func (as *SAgentStorage) waitVmToolsVersion(ctx context.Context, vm *esxi.SVirtualMachine) { + timeout := 90 * time.Second + + timeUpper := time.Now().Add(timeout) + for len(vm.GetToolsVersion()) == 0 && time.Now().Before(timeUpper) { + if vm.GetStatus() != api.VM_RUNNING { + vm.StartVM(ctx) + time.Sleep(5 * time.Second) + } + } + timeUpper = time.Now().Add(timeout) + for vm.GetStatus() == api.VM_RUNNING && time.Now().Before(timeUpper) { + vm.StopVM(ctx, true) + time.Sleep(5 * time.Second) + } + return +} + +type SHostDatastore struct { + HostIp string + Datastore models.SVCenterAccessInfo +} + +func (as *SAgentStorage) getHostAndDatastore(ctx context.Context, data SHostDatastore) (*esxi.SHost, *esxi.SDatastore, error) { + client, err := esxi.NewESXiClientFromAccessInfo(ctx, &data.Datastore) + if err != nil { + return nil, nil, errors.Wrap(err, "fail to generate client") + } + host, err := client.FindHostByIp(data.HostIp) + if err != nil { + return nil, nil, errors.Wrap(err, "fail to find host") + } + ds, err := host.FindDataStoreById(data.Datastore.PrivateId) + if err != nil { + return nil, nil, errors.Wrapf(err, "fail to find datastore") + } + return host, ds, nil +} + +func (as *SAgentStorage) isExist(dir string) bool { + _, err := os.Stat(dir) + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +func (as *SAgentStorage) checkDirC(dir string) error { + if as.isExist(dir) { + return nil + } + return os.MkdirAll(dir, 0770) +} + +func (as *SAgentStorage) checkFileR(file string) error { + if !as.isExist(file) { + return nil + } + return os.Remove(file) +} + +func (as *SAgentStorage) PrepareSaveToGlance(ctx context.Context, taskId string, diskInfo jsonutils.JSONObject) ( + ret jsonutils.JSONObject, err error) { + + type specStruct struct { + Vm models.SVCenterAccessInfo + Disk models.SVCenterAccessInfo + HostIp string + ImageId string + } + + spec := specStruct{} + err = diskInfo.Unmarshal(&spec) + if err != nil { + return nil, errors.Wrap(hostutils.ParamsError, err.Error()) + } + + destDir := as.GetImgsaveBackupPath() + as.checkDirC(destDir) + backupPath := filepath.Join(destDir, fmt.Sprintf("%s.%s", spec.Vm.PrivateId, taskId)) + defer as.checkFileR(backupPath) + + client, err := esxi.NewESXiClientFromAccessInfo(ctx, &spec.Vm) + if err != nil { + return nil, errors.Wrap(err, "esxi.NewESXiClientFromJson") + } + host, err := client.FindHostByIp(spec.HostIp) + if err != nil { + return nil, errors.Wrapf(err, "ESXiClient.FindHostByIp of ip '%s'", spec.HostIp) + } + ivm, err := host.GetIVMById(spec.Vm.PrivateId) + if err != nil { + return nil, errors.Wrapf(err, "esxi.SHost.GetIVMById for '%s'", spec.Vm.PrivateId) + } + vm := ivm.(*esxi.SVirtualMachine) + idisk, err := vm.GetIDiskById(spec.Disk.PrivateId) + if err != nil { + return nil, errors.Wrapf(err, "esxi.SVirtualMachine for '%s'", spec.Disk.PrivateId) + } + disk := idisk.(*esxi.SVirtualDisk) + log.Infof("Export VM image to %s", backupPath) + err = vm.ExportTemplate(ctx, disk.GetIndex(), backupPath) + if err != nil { + return nil, errors.Wrap(err, "VM.ExportTemplate") + } + dict := jsonutils.NewDict() + dict.Add(jsonutils.NewString(backupPath), "backup") + return dict, nil +} + +func (as *SAgentStorage) SaveToGlance(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + data, ok := params.(*jsonutils.JSONDict) + if !ok { + return nil, errors.Wrap(hostutils.ParamsError, "SaveToGlance params format error") + } + + var ( + imageId, _ = data.GetString("image_id") + imagePath, _ = data.GetString("image_path") + compress = jsonutils.QueryBoolean(data, "compress", true) + format, _ = data.GetString("format") + ) + log.Debugf("image path: %s", imagePath) + + if err := as.saveToGlance(ctx, imageId, imagePath, compress, format); err != nil { + log.Errorf("Save to glance failed: %s", err) + as.onSaveToGlanceFailed(ctx, imageId) + return nil, err + } + + imagecacheManager := as.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 (as *SAgentStorage) saveToGlance(ctx context.Context, imageId, imagePath string, + compress bool, format string) error { + ret, err := deployclient.GetDeployClient().SaveToGlance(context.Background(), + &deployapi.SaveToGlanceParams{DiskPath: imagePath, Compress: compress}) + if err != nil { + return errors.Wrap(err, "DeployClient.SaveToGlance") + } + + if compress { + origin, err := qemuimg.NewQemuImage(imagePath) + if err != nil { + log.Errorln(err) + return errors.Wrap(err, "qemuimg.NewQemuImage") + } + 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(ret.OsInfo) > 0 { + params.Set("os_type", jsonutils.NewString(ret.OsInfo)) + } + relInfo := ret.ReleaseInfo + 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, as.agent.GetZoneName()), + params, f, size) + if err != nil { + return errors.Wrap(err, "Images.Upload") + } + return nil +} + +func (as *SAgentStorage) onSaveToGlanceFailed(ctx context.Context, imageId string) { + params := jsonutils.NewDict() + params.Set("status", jsonutils.NewString("killed")) + _, err := modules.Images.Update(hostutils.GetImageSession(ctx, as.agent.GetZoneName()), + imageId, params) + if err != nil { + log.Errorln(err) + } +} diff --git a/pkg/hostman/storageman/storage_rbd.go b/pkg/hostman/storageman/storage_rbd.go index f29d934ce1..1e9f9e596d 100644 --- a/pkg/hostman/storageman/storage_rbd.go +++ b/pkg/hostman/storageman/storage_rbd.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux +// +build linux,cgo package storageman diff --git a/pkg/multicloud/esxi/datacenter.go b/pkg/multicloud/esxi/datacenter.go index 9b908f0e3f..b26c24b19d 100644 --- a/pkg/multicloud/esxi/datacenter.go +++ b/pkg/multicloud/esxi/datacenter.go @@ -175,7 +175,8 @@ func (dc *SDatacenter) fetchVms(vmRefs []types.ManagedObjectReference, all bool) } } - retVms := make([]cloudprovider.ICloudVM, 0) + // avoid applying new memory and copying + retVms := make([]cloudprovider.ICloudVM, 0, len(vms)) for i := 0; i < len(vms); i += 1 { if all || !strings.HasPrefix(vms[i].Entity().Name, api.ESXI_IMAGE_CACHE_TMP_PREFIX) { vmObj := NewVirtualMachine(dc.manager, &vms[i], dc) @@ -187,6 +188,22 @@ func (dc *SDatacenter) fetchVms(vmRefs []types.ManagedObjectReference, all bool) return retVms, nil } +func (dc *SDatacenter) fetchDatastores(datastoreRefs []types.ManagedObjectReference) ([]cloudprovider.ICloudStorage, error) { + var dss []mo.Datastore + if datastoreRefs != nil { + err := dc.manager.references2Objects(datastoreRefs, DATASTORE_PROPS, &dss) + if err != nil { + return nil, errors.Wrap(err, "dc.manager.references2Objects") + } + } + + retDatastores := make([]cloudprovider.ICloudStorage, 0, len(dss)) + for i := range dss { + retDatastores = append(retDatastores, NewDatastore(dc.manager, &dss[i], dc)) + } + return retDatastores, nil +} + func (dc *SDatacenter) scanNetworks() error { if dc.inetworks == nil { if dc.isDefaultDc() { diff --git a/pkg/multicloud/esxi/device.go b/pkg/multicloud/esxi/device.go index b02f8582ee..46d179a99d 100644 --- a/pkg/multicloud/esxi/device.go +++ b/pkg/multicloud/esxi/device.go @@ -43,7 +43,7 @@ func (dev *SVirtualDevice) getControllerKey() int32 { return dev.dev.GetVirtualDevice().ControllerKey } -func (dev *SVirtualDevice) getIndex() int { +func (dev *SVirtualDevice) GetIndex() int { return dev.index } diff --git a/pkg/multicloud/esxi/devtools.go b/pkg/multicloud/esxi/devtools.go index ec05faabad..bb296af1ea 100644 --- a/pkg/multicloud/esxi/devtools.go +++ b/pkg/multicloud/esxi/devtools.go @@ -15,7 +15,12 @@ package esxi import ( + "fmt" + "reflect" + "github.com/vmware/govmomi/vim25/types" + + "yunion.io/x/pkg/errors" ) func NewDiskDev(sizeMb int64, templatePath string, uuid string, index int32, key int32, controlKey int32) *types.VirtualDisk { @@ -55,3 +60,142 @@ func addDevSpec(device types.BaseVirtualDevice) *types.VirtualDeviceConfigSpec { spec.Device = device return &spec } + +func NewSCSIDev(key, ctlKey int32, driver string) types.BaseVirtualDevice { + desc := types.Description{Label: "SCSI controller 0", Summary: "VMware virtual SCSI"} + + if driver == "pvscsi" { + device := types.ParaVirtualSCSIController{} + device.DeviceInfo = &desc + device.Key = key + device.ControllerKey = ctlKey + device.SharedBus = "noSharing" + return &device + } + device := types.VirtualLsiLogicController{} + device.DeviceInfo = &desc + device.Key = key + device.ControllerKey = ctlKey + device.SharedBus = "noSharing" + return &device +} + +func NewAHCIDev(key, ctlKey int32) types.BaseVirtualDevice { + device := types.VirtualAHCIController{} + device.DeviceInfo = &types.Description{Label: "SATA controller 0", Summary: "AHCI"} + device.ControllerKey = ctlKey + device.Key = key + return &device +} + +func NewSVGADev(key, ctlKey int32) types.BaseVirtualDevice { + device := types.VirtualMachineVideoCard{} + device.DeviceInfo = &types.Description{Label: "Video card", Summary: "Video card"} + device.ControllerKey = ctlKey + device.Key = key + device.VideoRamSizeInKB = 16 * 1024 + return &device +} + +func NewIDEDev(key, index int32) types.BaseVirtualDevice { + device := types.VirtualIDEController{} + s := fmt.Sprintf("IDE %d", index) + device.DeviceInfo = &types.Description{Label: s, Summary: s} + device.Key = key + index + device.BusNumber = index + return &device +} + +func NewCDROMDev(path string, key, ctlKey int32) types.BaseVirtualDevice { + device := types.VirtualCdrom{} + device.DeviceInfo = &types.Description{Label: "CD/DVD drive 1", Summary: "Local ISO Emulated CD-ROM"} + device.ControllerKey = ctlKey + device.Key = key + + connectable := types.VirtualDeviceConnectInfo{AllowGuestControl: true, Status: "untried"} + if len(path) != 0 { + device.Backing = &types.VirtualCdromIsoBackingInfo{types.VirtualDeviceFileBackingInfo{FileName: path}} + connectable.StartConnected = true + } else { + device.Backing = &types.VirtualCdromRemoteAtapiBackingInfo{} + connectable.StartConnected = false + } + + device.Connectable = &connectable + return &device +} + +func NewVNICDev(host *SHost, mac, driver string, vlanId int32, key, ctlKey, index int32) (types.BaseVirtualDevice, error) { + desc := types.Description{Label: fmt.Sprintf("Network adapter %d", index+1), Summary: "VM Network"} + + inet, err := host.FindNetworkByVlanID(vlanId) + if err != nil { + return nil, errors.Wrap(err, "SHost.FindNetworkByVlanID") + } + if inet == nil { + return nil, errors.Error(fmt.Sprintf("VLAN %d not found", vlanId)) + } + + var ( + False = false + True = true + ) + var backing types.BaseVirtualDeviceBackingInfo + switch inet.(type) { + case *SDistributedVirtualPortgroup: + net := inet.(*SDistributedVirtualPortgroup) + port, err := net.FindPort() + if err != nil { + return nil, errors.Wrap(err, "net.FindPort") + } + if port == nil { + return nil, errors.Error("no valid port on DVS, exhausted") + } + portCon := types.DistributedVirtualSwitchPortConnection{ + PortgroupKey: port.PortgroupKey, + SwitchUuid: port.DvsUuid, + PortKey: port.Key, + } + backing = &types.VirtualEthernetCardDistributedVirtualPortBackingInfo{Port: portCon} + case *SNetwork: + monet := inet.(*SNetwork).getMONetwork() + backing = &types.VirtualEthernetCardNetworkBackingInfo{ + VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{ + DeviceName: monet.Name, + UseAutoDetect: &False, + }, + Network: &monet.Self, + } + default: + return nil, errors.Error(fmt.Sprintf("Unsuppport network type %s", reflect.TypeOf(inet).Name())) + } + + connectable := types.VirtualDeviceConnectInfo{ + StartConnected: true, + AllowGuestControl: true, + Connected: false, + Status: "untried", + } + + nic := types.VirtualEthernetCard{ + VirtualDevice: types.VirtualDevice{ + DeviceInfo: &desc, + Backing: backing, + }, + WakeOnLanEnabled: &True, + } + nic.Connectable = &connectable + nic.ControllerKey = ctlKey + nic.Key = key + index + if len(mac) != 0 { + nic.AddressType = "Manual" + nic.MacAddress = mac + } else { + nic.AddressType = "Generated" + } + if driver == "e1000" { + return &types.VirtualE1000{nic}, nil + } + + return &types.VirtualVmxnet3{types.VirtualVmxnet{nic}}, nil +} diff --git a/pkg/multicloud/esxi/host.go b/pkg/multicloud/esxi/host.go index 2c383a1a74..06676c4aec 100644 --- a/pkg/multicloud/esxi/host.go +++ b/pkg/multicloud/esxi/host.go @@ -21,11 +21,13 @@ import ( "strings" "time" + "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/regutils" @@ -34,7 +36,7 @@ import ( "yunion.io/x/onecloud/pkg/multicloud" ) -var HOST_SYSTEM_PROPS = []string{"name", "parent", "summary", "config", "hardware", "vm", "datastore"} +var HOST_SYSTEM_PROPS = []string{"name", "parent", "summary", "config", "hardware", "vm", "datastore", "network"} type SHostStorageAdapterInfo struct { Device string @@ -89,6 +91,10 @@ type SHost struct { storageCache *SDatastoreImageCache vms []cloudprovider.ICloudVM + + parent *mo.ComputeResource + + networks []SNetwork } func NewHost(manager *SESXiClient, host *mo.HostSystem, dc *SDatacenter) *SHost { @@ -587,6 +593,205 @@ func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudpr return nil, cloudprovider.ErrNotImplemented } +func (self *SHost) DoCreateVM(ctx context.Context, ds *SDatastore, data *jsonutils.JSONDict) (*SVirtualMachine, error) { + var ( + deviceChange = make([]types.BaseVirtualDeviceConfigSpec, 0, 5) + name string + osName = "Linux" + memoryMB int64 = 2048 + numCPUs int32 = 2 + bios string + ) + + if data.Contains("uuid") { + name, _ = data.GetString("uuid") + } else { + name, _ = data.GetString("name") + } + if data.Contains("os_name") { + osName, _ = data.GetString("os_name") + } + datastorePath := fmt.Sprintf("[%s] %s", ds.GetRelName(), name) + if data.Contains("mem") { + memoryMB, _ = data.Int("mem") + } + if data.Contains("cpu") { + cpunum, _ := data.Int("cpu") + numCPUs = int32(cpunum) + } + + firmware := "" + if data.Contains("bios") { + bios, _ = data.GetString("bios") + } + if len(bios) != 0 { + if bios == "BIOS" { + firmware = "bios" + } else if bios == "UEFI" { + firmware = "efi" + } + } + + guestId := "rhel6_64Guest" + if osName == "Windows" { + guestId = "windows7Server64Guest" + } + + version := "vmx-10" + if self.isVersion50() { + version = "vmx-08" + } + + uuid, _ := data.GetString("uuid") + + spec := types.VirtualMachineConfigSpec{ + Name: name, + Version: version, + Uuid: uuid, + GuestId: guestId, + NumCPUs: numCPUs, + MemoryMB: memoryMB, + Firmware: firmware, + } + spec.Files = &types.VirtualMachineFileInfo{ + VmPathName: datastorePath, + } + + deviceChange = append(deviceChange, addDevSpec(NewIDEDev(200, 0))) + deviceChange = append(deviceChange, addDevSpec(NewIDEDev(200, 1))) + deviceChange = append(deviceChange, addDevSpec(NewSVGADev(500, 100))) + disks, _ := data.GetArray("disks") + driver := "scsi" + if len(disks) > 0 { + driver, _ = disks[0].GetString("driver") + } + if driver == "scsi" || driver == "pvscsi" { + if self.isVersion50() { + driver = "scsi" + } + deviceChange = append(deviceChange, addDevSpec(NewSCSIDev(1000, 100, driver))) + } + cdromPath := "" + if data.Contains("cdrom") { + tmp, _ := data.Get("cdrom") + cdromPath, _ = tmp.GetString("path") + } + var err error + if len(cdromPath) != 0 && !strings.HasPrefix(cdromPath, "[") { + cdromPath, err = self.FileUrlPathToDsPath(cdromPath) + if err != nil { + return nil, errors.Wrapf(err, "SHost.FileUrlPathToDsPath for cdrom path '%s'", cdromPath) + } + } + deviceChange = append(deviceChange, addDevSpec(NewCDROMDev(cdromPath, 16000, 201))) + + var ( + scsiIdx = 0 + ideIdx = 0 + index = 0 + ctrlKey = 0 + ) + for _, disk := range disks { + imagePath, _ := disk.GetString("image_path") + var size int64 = 0 + if len(imagePath) == 0 { + size, _ = disk.Int("size") + if size == 0 { + size = 30 * 1024 + } + } else { + imagePath, err = self.FileUrlPathToDsPath(imagePath) + if err != nil { + return nil, errors.Wrapf(err, "SHost.FileUrlPathToDsPath for image path '%s'", imagePath) + } + } + uuid, _ := disk.GetString("disk_id") + driver := "scsi" + if disk.Contains("driver") { + driver, _ = disk.GetString("driver") + } + if driver == "scsi" || driver == "pvscsi" { + if self.isVersion50() { + driver = "scsi" + } + ctrlKey = 1000 + index = scsiIdx + scsiIdx += 1 + } else { + ctrlKey = 200 + ideIdx/2 + index = ideIdx % 2 + ideIdx += 1 + } + log.Debugf("size: %d, image path: %s, uuid: %s, index: %d, ctrlKey: %d", size, imagePath, uuid, index, ctrlKey) + spec := addDevSpec(NewDiskDev(size, imagePath, uuid, int32(index), 2000, int32(ctrlKey))) + spec.FileOperation = "create" + deviceChange = append(deviceChange, spec) + } + + nics, _ := data.GetArray("nics") + for _, nic := range nics { + index, _ := nic.Int("index") + mac, _ := nic.GetString("mac") + driver := "e1000" + if nic.Contains("driver") { + driver, _ = nic.GetString("driver") + } + if self.isVersion50() { + driver = "e1000" + } + var vlanId int64 = 1 + if nic.Contains("vlan") { + vlanId, _ = nic.Int("vlan") + } + dev, err := NewVNICDev(self, mac, driver, int32(vlanId), 4000, 100, int32(index)) + if err != nil { + return nil, errors.Wrap(err, "NewVNICDev") + } + deviceChange = append(deviceChange, addDevSpec(dev)) + } + + spec.DeviceChange = deviceChange + dc, err := self.GetDatacenter() + if err != nil { + return nil, errors.Wrapf(err, "SHost.GetDatacenter for host '%s'", self.GetId()) + } + // get vmFloder + folders, err := dc.getObjectDatacenter().Folders(ctx) + if err != nil { + return nil, errors.Wrap(err, "object.DataCenter.Folders") + } + vmFolder := folders.VmFolder + resourcePool, err := self.GetResourcePool() + if err != nil { + return nil, errors.Wrap(err, "SHost.GetResourcePool") + } + task, err := vmFolder.CreateVM(ctx, spec, resourcePool, self.GetoHostSystem()) + if err != nil { + return nil, errors.Wrap(err, "VmFolder.Create") + } + + info, err := task.WaitForResult(ctx, nil) + if err != nil { + return nil, errors.Wrap(err, "Task.WaitForResult") + } + + var moVM mo.VirtualMachine + err = self.manager.reference2Object(info.Result.(types.ManagedObjectReference), VIRTUAL_MACHINE_PROPS, &moVM) + if err != nil { + return nil, errors.Wrap(err, "fail to fetch virtual machine just created") + } + + return NewVirtualMachine(self.manager, &moVM, self.datacenter), nil +} + +func (host *SHost) isVersion50() bool { + version := host.GetVersion() + if strings.HasPrefix(version, "5.") { + return true + } + return false +} + func (host *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) { nics := host.getNicInfo() inics := make([]cloudprovider.ICloudHostNetInterface, len(nics)) @@ -662,3 +867,212 @@ func (host *SHost) GetManagementServerIp() string { func (host *SHost) IsManagedByVCenter() bool { return len(host.getHostSystem().Summary.ManagementServerIp) > 0 } + +func (host *SHost) FindDataStoreById(id string) (*SDatastore, error) { + datastores, err := host.GetDataStores() + if err != nil { + return nil, err + } + for i := range datastores { + if datastores[i].GetGlobalId() == id { + return datastores[i].(*SDatastore), nil + } + } + return nil, fmt.Errorf("no such datastore %s", id) +} + +func (host *SHost) GetDataStores() ([]cloudprovider.ICloudStorage, error) { + err := host.fetchDatastores() + if err != nil { + return nil, err + } + return host.datastores, nil +} + +func (host *SHost) fetchDatastores() error { + if host.datastores != nil { + return nil + } + + dc, err := host.GetDatacenter() + if err != nil { + return err + } + + MAX_TRIES := 3 + for tried := 0; tried < MAX_TRIES; tried += 1 { + hostDss := host.getHostSystem().Datastore + if len(hostDss) == 0 { + // log.Errorf("host VMs are nil!!!!!") + return nil + } + + dss, err := dc.fetchDatastores(hostDss) + if err != nil { + log.Errorf("dc.fetchVms fail %s", err) + time.Sleep(time.Second) + host.Refresh() + continue + } + host.datastores = dss + break + } + return nil +} + +func (host *SHost) FileUrlPathToDsPath(path string) (string, error) { + var newPath string + dss, err := host.GetDataStores() + if err != nil { + return newPath, err + } + for _, ds := range dss { + rds := ds.(*SDatastore) + if strings.HasPrefix(path, rds.GetUrl()) { + newPath = fmt.Sprintf("[%s] %s", rds.GetRelName(), path[len(rds.GetUrl()):]) + } + break + } + return newPath, nil +} + +func (host *SHost) FindNetworkByVlanID(vlanID int32) (IVMNetwork, error) { + if vlanID > 1 && vlanID < 4095 { + return host.findVlanDVPG(int32(vlanID)) + } + n, err := host.findNovlanDVPG() + if err != nil { + return nil, err + } + if n != nil { + return n, err + } + return host.findBasicNetwork() +} + +func (host *SHost) findBasicNetwork() (*SNetwork, error) { + nets, err := host.GetNetwork() + if err != nil { + return nil, err + } + if len(nets) == 0 { + return nil, nil + } + return &nets[0], nil +} + +func (host *SHost) GetNetwork() ([]SNetwork, error) { + if host.networks != nil { + return host.networks, nil + } + netMobs := host.getHostSystem().Network + moNets := make([]mo.Network, 0) + err := host.manager.references2Objects(netMobs, NETWORK_PROPS, &moNets) + if err != nil { + return nil, errors.Wrap(err, "references2Objects") + } + nets := make([]SNetwork, len(moNets)) + for i := range moNets { + nets[i] = *NewNetwork(host.manager, &moNets[i], host.datacenter) + } + host.networks = nets + return host.networks, nil +} + +func (host *SHost) findNovlanDVPG() (*SDistributedVirtualPortgroup, error) { + nets, err := host.datacenter.GetNetworks() + if err != nil { + return nil, errors.Wrap(err, "SHost.datacenter.GetNetworks") + } + for _, net := range nets { + dvpg, ok := net.(*SDistributedVirtualPortgroup) + if !ok || !dvpg.ContainHost(host) || len(dvpg.GetActivePorts()) == 0 { + continue + } + nvlan := dvpg.GetVlanId() + if nvlan <= 1 || nvlan >= 4095 { + return dvpg, nil + } + } + return nil, nil +} + +func (host *SHost) findVlanDVPG(vlanId int32) (*SDistributedVirtualPortgroup, error) { + nets, err := host.datacenter.GetNetworks() + if err != nil { + return nil, errors.Wrap(err, "SHost.datacenter.GetNetworks") + } + for _, net := range nets { + dvpg, ok := net.(*SDistributedVirtualPortgroup) + if !ok || !dvpg.ContainHost(host) || len(dvpg.GetActivePorts()) == 0 { + continue + } + nvlan := dvpg.GetVlanId() + if nvlan == vlanId { + return dvpg, nil + } + } + return nil, nil +} + +func (host *SHost) GetoHostSystem() *object.HostSystem { + return object.NewHostSystem(host.manager.client.Client, host.getHostSystem().Reference()) +} + +func (host *SHost) GetResourcePool() (*object.ResourcePool, error) { + var err error + if host.parent == nil { + host.parent, err = host.getResourcePool() + if err != nil { + return nil, err + } + } + return object.NewResourcePool(host.manager.client.Client, *host.parent.ResourcePool), nil +} + +func (host *SHost) getResourcePool() (*mo.ComputeResource, error) { + var mcr *mo.ComputeResource + var parent interface{} + + moHost := host.getHostSystem() + + switch moHost.Parent.Type { + case "ComputeResource": + mcr = new(mo.ComputeResource) + parent = mcr + case "ClusterComputeResource": + mcc := new(mo.ClusterComputeResource) + mcr = &mcc.ComputeResource + parent = mcc + default: + return nil, errors.Error(fmt.Sprintf("unknown host parent type: %s", moHost.Parent.Type)) + } + + err := host.manager.reference2Object(*moHost.Parent, []string{"resourcePool"}, parent) + if err != nil { + return nil, errors.Wrap(err, "SESXiClient.reference2Object") + } + return mcr, nil +} + +func (host *SHost) GetCluster() (*mo.ComputeResource, error) { + return host.getResourcePool() +} + +func (host *SHost) GetSiblingHosts() ([]*SHost, error) { + rp, err := host.GetCluster() + if err != nil { + return nil, err + } + moHosts := make([]mo.HostSystem, 0, len(rp.Host)) + err = host.manager.references2Objects(rp.Host, HOST_SYSTEM_PROPS, &moHosts) + if err != nil { + return nil, errors.Wrap(err, "SESXiClient.references2Objects") + } + + ret := make([]*SHost, len(moHosts)) + for i := range moHosts { + ret[i] = NewHost(host.manager, &moHosts[i], host.datacenter) + } + return ret, nil +} diff --git a/pkg/multicloud/esxi/logger.go b/pkg/multicloud/esxi/logger.go new file mode 100644 index 0000000000..5b0e49d27a --- /dev/null +++ b/pkg/multicloud/esxi/logger.go @@ -0,0 +1,74 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package esxi + +import ( + "github.com/vmware/govmomi/vim25/progress" + + "yunion.io/x/log" +) + +type logger struct { + name string + chid chan progress.Report + over chan struct{} +} + +func (l *logger) Sink() chan<- progress.Report { + return l.chid +} + +func (l *logger) End() { + close(l.over) +} + +func newLeaseLogger(name string, cap int) *logger { + return &logger{ + name: name, + chid: make(chan progress.Report, cap), + over: make(chan struct{}), + } +} + +func (l *logger) Log() { + go func() { + var pre float32 = 0 + + log.Debugf("logger.Log...") + Loop: + for { + select { + case r, ok := <-l.chid: + if !ok { + break Loop + } + if r.Error() != nil { + log.Errorf("%s report, error: %s", l.name, r.Error()) + break Loop + } + if r.Percentage() >= pre { + log.Debugf("%s report: speed: %s, percentage: %f%%", l.name, r.Detail(), pre) + pre += 10 + } + if r.Percentage() >= 110 { + break Loop + } + case <-l.over: + break Loop + } + + } + }() +} diff --git a/pkg/multicloud/esxi/manager.go b/pkg/multicloud/esxi/manager.go index fc5137c22b..b0c27e7163 100644 --- a/pkg/multicloud/esxi/manager.go +++ b/pkg/multicloud/esxi/manager.go @@ -26,15 +26,19 @@ import ( "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/session" "github.com/vmware/govmomi/view" + "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/hostman/hostutils" "yunion.io/x/onecloud/pkg/multicloud" ) @@ -98,6 +102,34 @@ func NewESXiClient2(providerId string, providerName string, host string, port in return cli, nil } +func NewESXiClientFromJson(ctx context.Context, input jsonutils.JSONObject) (*SESXiClient, *models.SVCenterAccessInfo, error) { + accessInfo := new(models.SVCenterAccessInfo) + err := input.Unmarshal(accessInfo) + if err != nil { + return nil, nil, errors.Wrapf(err, "%s: unmarshal to SVCenterAccessInfo", hostutils.ParamsError) + } + c, err := NewESXiClientFromAccessInfo(ctx, accessInfo) + return c, accessInfo, err +} + +func NewESXiClientFromAccessInfo(ctx context.Context, accessInfo *models.SVCenterAccessInfo) (*SESXiClient, error) { + if len(accessInfo.VcenterId) > 0 { + tmp, err := utils.DescryptAESBase64(accessInfo.VcenterId, accessInfo.Password) + if err == nil { + accessInfo.Password = tmp + } + } + client, err := NewESXiClient("", "", accessInfo.Host, accessInfo.Port, accessInfo.Account, accessInfo.Password) + if err != nil { + return nil, err + } + if ctx == nil { + ctx = context.Background() + } + client.context = ctx + return client, nil +} + func (cli *SESXiClient) getUrl() string { if cli.port == 443 || cli.port == 0 { return fmt.Sprintf("https://%s", cli.host) @@ -419,3 +451,67 @@ func (cli *SESXiClient) GetCapabilities() []string { } return caps } + +func (cli *SESXiClient) FindVMByPrivateID(idstr string) (*SVirtualMachine, error) { + searchIndex := object.NewSearchIndex(cli.client.Client) + instanceUuid := true + vmRef, err := searchIndex.FindByUuid(cli.context, nil, idstr, true, &instanceUuid) + if err != nil { + return nil, errors.Wrap(err, "searchIndex.FindByUuid fail") + } + if vmRef == nil { + return nil, fmt.Errorf("cannot find %s", idstr) + } + var vm mo.VirtualMachine + err = cli.reference2Object(vmRef.Reference(), VIRTUAL_MACHINE_PROPS, &vm) + if err != nil { + return nil, errors.Wrap(err, "reference2Object fail") + } + + return NewVirtualMachine(cli, &vm, nil), nil +} + +func (cli *SESXiClient) DoExtendDiskOnline(_vm *SVirtualMachine, _disk *SVirtualDisk, newSizeMb int64) error { + disk := _disk.getVirtualDisk() + disk.CapacityInKB = newSizeMb * 1024 + devSepc := types.VirtualDeviceConfigSpec{Operation: types.VirtualDeviceConfigSpecOperationEdit, Device: disk} + spec := types.VirtualMachineConfigSpec{DeviceChange: []types.BaseVirtualDeviceConfigSpec{&devSepc}} + vm := object.NewVirtualMachine(cli.client.Client, _vm.getVirtualMachine().Reference()) + task, err := vm.Reconfigure(cli.context, spec) + if err != nil { + return errors.Wrapf(err, "vm reconfigure failed") + } + return task.Wait(cli.context) +} + +func (cli *SESXiClient) ExtendDisk(url string, newSizeMb int64) error { + param := types.ExtendVirtualDisk_Task{ + This: *cli.client.Client.ServiceContent.VirtualDiskManager, + Name: url, + NewCapacityKb: newSizeMb * 1024, + } + response, err := methods.ExtendVirtualDisk_Task(cli.context, cli.client, ¶m) + if err != nil { + return errors.Wrapf(err, "extend virtualdisk task failed") + } + log.Debugf("extend virtual disk task response: %s", response.Returnval.String()) + return nil +} + +func (cli *SESXiClient) CopyDisk(ctx context.Context, src, dst string, isForce bool) error { + dm := object.NewVirtualDiskManager(cli.client.Client) + task, err := dm.CopyVirtualDisk(ctx, src, nil, dst, nil, nil, isForce) + if err != nil { + return err + } + return task.Wait(ctx) +} + +func (cli *SESXiClient) MoveDisk(ctx context.Context, src, dst string, isForce bool) error { + dm := object.NewVirtualDiskManager(cli.client.Client) + task, err := dm.MoveVirtualDisk(ctx, src, nil, dst, nil, isForce) + if err != nil { + return err + } + return task.Wait(ctx) +} diff --git a/pkg/multicloud/esxi/network.go b/pkg/multicloud/esxi/network.go index 9b05cf0119..5a6d04c739 100644 --- a/pkg/multicloud/esxi/network.go +++ b/pkg/multicloud/esxi/network.go @@ -15,8 +15,13 @@ package esxi import ( + "strings" + + "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" + + "yunion.io/x/pkg/errors" ) type IVMNetwork interface { @@ -26,6 +31,7 @@ type IVMNetwork interface { GetNumPorts() int32 GetActivePorts() []string GetType() string + ContainHost(host *SHost) bool } const ( @@ -85,6 +91,17 @@ func (net *SNetwork) GetActivePorts() []string { return nil } +func (net *SNetwork) ContainHost(host *SHost) bool { + objs := net.getMONetwork().Host + moHost := host.getHostSystem() + for _, obj := range objs { + if obj.Value == moHost.Reference().Value { + return true + } + } + return false +} + func (net *SDistributedVirtualPortgroup) getMODVPortgroup() *mo.DistributedVirtualPortgroup { return net.object.(*mo.DistributedVirtualPortgroup) } @@ -142,3 +159,116 @@ func (net *SDistributedVirtualPortgroup) GetActivePorts() []string { } return nil } + +func (net *SDistributedVirtualPortgroup) ContainHost(host *SHost) bool { + objs := net.getMODVPortgroup().Host + moHost := host.getHostSystem() + for _, obj := range objs { + if obj.Value == moHost.Reference().Value { + return true + } + } + return false +} + +func (net *SDistributedVirtualPortgroup) Uplink() bool { + dvpg := net.getMODVPortgroup() + return *dvpg.Config.Uplink +} + +func (net *SDistributedVirtualPortgroup) FindPort() (*types.DistributedVirtualPort, error) { + dvgp := net.getMODVPortgroup() + odvs := object.NewDistributedVirtualSwitch(net.manager.client.Client, *dvgp.Config.DistributedVirtualSwitch) + var ( + False = false + True = true + ) + criteria := types.DistributedVirtualSwitchPortCriteria{ + Connected: &False, + Inside: &True, + PortgroupKey: []string{dvgp.Key}, + } + ports, err := odvs.FetchDVPorts(net.manager.context, &criteria) + if err != nil { + return nil, errors.Wrap(err, "object.DVS.FetchDVPorts") + } + if len(ports) > 0 { + // release extra space timely + return &ports[:1][0], nil + } + return nil, nil +} + +func (net *SDistributedVirtualPortgroup) AddHostToDVS(host *SHost) (err error) { + // get dvs + dvgp := net.getMODVPortgroup() + var s mo.DistributedVirtualSwitch + err = net.manager.reference2Object(*dvgp.Config.DistributedVirtualSwitch, []string{"config"}, &s) + if err != nil { + return errors.Wrapf(err, "fail to convert reference to object") + } + moHost := host.getHostSystem() + + // check firstly + for _, host := range s.Config.GetDVSConfigInfo().Host { + if host.Config.Host.Value == moHost.Reference().Value { + // host is already a member of dvs + return nil + } + } + config := &types.DVSConfigSpec{ConfigVersion: s.Config.GetDVSConfigInfo().ConfigVersion} + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + pnics := moHost.Config.Network.Pnic + + if len(pnics) == 0 { + return errors.Error("no pnic in this host") + } + + // try one by one + // have some bug + for i := 0; i < len(pnics); i++ { + backing.PnicSpec = []types.DistributedVirtualSwitchHostMemberPnicSpec{ + { + PnicDevice: pnics[i].Device, + }, + } + config.Host = []types.DistributedVirtualSwitchHostMemberConfigSpec{ + { + Operation: "add", + Host: moHost.Reference(), + Backing: backing, + }, + } + var task *object.Task + dvs := object.NewDistributedVirtualSwitch(net.manager.client.Client, s.Reference()) + task, err = dvs.Reconfigure(net.manager.context, config) + if err != nil { + return errors.Wrapf(err, "dvs.Reconfigure") + } + err = task.Wait(net.manager.context) + if err == nil { + return nil + } + if strings.Contains(err.Error(), "concurrent modification") { + i -= 1 + } + } + return err +} + +func FindVlanDistVswitch(nets []IVMNetwork, vlanID int32) IVMNetwork { + for _, net := range nets { + _, ok := net.(*SDistributedVirtualPortgroup) + if !ok { + continue + } + if len(net.GetActivePorts()) == 0 { + continue + } + nvlan := net.GetVlanId() + if nvlan == vlanID { + return net + } + } + return nil +} diff --git a/pkg/multicloud/esxi/storage.go b/pkg/multicloud/esxi/storage.go index 732902cf95..bdc691a331 100644 --- a/pkg/multicloud/esxi/storage.go +++ b/pkg/multicloud/esxi/storage.go @@ -15,20 +15,30 @@ package esxi import ( + "bytes" "context" + "encoding/binary" "fmt" "io" "io/ioutil" + "math/rand" "net/http" "net/url" + "os" "path" + "path/filepath" "regexp" "strconv" "strings" + "text/template" "time" + "unicode" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/ovf" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/progress" + "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" "yunion.io/x/jsonutils" @@ -80,6 +90,10 @@ func (self *SDatastore) GetName() string { return fmt.Sprintf("%s-%s", self.getVolumeType(), volName) } +func (self *SDatastore) GetRelName() string { + return self.getDatastore().Info.GetDatastoreInfo().Name +} + func (self *SDatastore) GetCapacityMB() int64 { moStore := self.getDatastore() return moStore.Summary.Capacity / 1024 / 1024 @@ -701,6 +715,21 @@ func (self *SDatastore) RemoveDir(ctx context.Context, remotePath string) error return dnm.DeleteDirectory(ctx, dcObj, remotePath) } +// CheckDirC will check that Dir 'remotePath' is exist, if not, create one. +func (self *SDatastore) CheckDirC(remotePath string) error { + _, err := self.CheckFile(context.Background(), remotePath) + if err == nil { + return nil + } + if errors.Cause(err) != cloudprovider.ErrNotFound { + return err + } + m := object.NewFileManager(self.manager.client.Client) + path := fmt.Sprintf("[%s] %s", self.GetRelName(), remotePath) + return m.MakeDirectory(self.manager.context, path, self.datacenter.getObjectDatacenter(), + true) +} + func (self *SDatastore) IsSysDiskStore() bool { return true } @@ -717,3 +746,242 @@ func (self *SDatastore) MoveVmdk(ctx context.Context, srcPath string, dstPath st } return task.Wait(ctx) } + +// domainName will abandon the rune which should't disapear +func (self *SDatastore) domainName(name string) string { + var b bytes.Buffer + for _, r := range name { + if b.Len() == 0 { + if unicode.IsLetter(r) { + b.WriteRune(r) + } + } else { + if unicode.IsDigit(r) || unicode.IsLetter(r) || r == '-' { + b.WriteRune(r) + } + } + } + return strings.TrimRight(b.String(), "-") +} + +// ImportVMDK will upload local vmdk 'diskFile' to the 'remotePath' of remote datastore +func (self *SDatastore) ImportVMDK(ctx context.Context, diskFile, remotePath string, host *SHost) error { + name := fmt.Sprintf("yunioncloud.%s%d", self.domainName(remotePath), rand.Int()) + vm, err := self.ImportVM(ctx, diskFile, name, host) + if err != nil { + return errors.Wrap(err, "SDatastore.ImportVM") + } + + defer func() { + task, err := vm.Destroy(ctx) + if err != nil { + log.Errorf("vm.Destory: %s", err) + return + } + + if err = task.Wait(ctx); err != nil { + log.Errorf("task.Wait: %s", err) + } + }() + + // check if 'image_cache' is esixt + err = self.CheckDirC("image_cache") + if err != nil { + return errors.Wrap(err, "SDatastore.CheckDirC") + } + + fm := self.getDatastoreObj().NewFileManager(self.datacenter.getObjectDatacenter(), true) + + // if image_cache not exist + return fm.Move(ctx, fmt.Sprintf("[%s] %s/%s.vmdk", self.GetRelName(), name, name), fmt.Sprintf("[%s] %s", + self.GetRelName(), remotePath)) +} + +var ( + ErrInvalidFormat = errors.Error("vmdk: invalid format (must be streamOptimized)") +) + +// info is used to inspect a vmdk and generate an ovf template +type info struct { + Header struct { + MagicNumber uint32 + Version uint32 + Flags uint32 + Capacity uint64 + } + + Capacity uint64 + Size int64 + Name string + ImportName string +} + +// stat looks at the vmdk header to make sure the format is streamOptimized and +// extracts the disk capacity required to properly generate the ovf descriptor. +func stat(name string) (*info, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + + var ( + di info + buf bytes.Buffer + ) + + _, err = io.CopyN(&buf, f, int64(binary.Size(di.Header))) + fi, _ := f.Stat() + _ = f.Close() + if err != nil { + return nil, err + } + + err = binary.Read(&buf, binary.LittleEndian, &di.Header) + if err != nil { + return nil, err + } + + if di.Header.MagicNumber != 0x564d444b { // SPARSE_MAGICNUMBER + return nil, ErrInvalidFormat + } + + if di.Header.Flags&(1<<16) == 0 { // SPARSEFLAG_COMPRESSED + // Needs to be converted, for example: + // vmware-vdiskmanager -r src.vmdk -t 5 dst.vmdk + // qemu-img convert -O vmdk -o subformat=streamOptimized src.vmdk dst.vmdk + return nil, ErrInvalidFormat + } + + di.Capacity = di.Header.Capacity * 512 // VMDK_SECTOR_SIZE + di.Size = fi.Size() + di.Name = filepath.Base(name) + di.ImportName = strings.TrimSuffix(di.Name, ".vmdk") + + return &di, nil +} + +// ovf returns an expanded descriptor template +func (di *info) ovf() (string, error) { + var buf bytes.Buffer + + tmpl, err := template.ParseFiles("/opt/yunion/share/vmware/ovf.xml") + if err != nil { + return "", err + } + + err = tmpl.Execute(&buf, di) + if err != nil { + return "", err + } + + return buf.String(), nil +} + +// ImportParams contains the set of optional params to the Import function. +// Note that "optional" may depend on environment, such as ESX or vCenter. +type ImportParams struct { + Path string + Logger progress.Sinker + Type types.VirtualDiskType + Force bool + Datacenter *object.Datacenter + Pool *object.ResourcePool + Folder *object.Folder + Host *object.HostSystem +} + +// ImportVM will import a vm by uploading a local vmdk +func (self *SDatastore) ImportVM(ctx context.Context, diskFile, name string, host *SHost) (*object.VirtualMachine, error) { + + var ( + c = self.manager.client.Client + datastore = self.getDatastoreObj() + ) + + m := ovf.NewManager(c) + + disk, err := stat(diskFile) + if err != nil { + return nil, errors.Wrap(err, "stat") + } + + disk.ImportName = name + + // Expand the ovf template + descriptor, err := disk.ovf() + if err != nil { + return nil, errors.Wrap(err, "disk.ovf") + } + + folders, err := self.datacenter.getObjectDatacenter().Folders(ctx) + if err != nil { + return nil, errors.Wrap(err, "Folders") + } + pool, err := host.GetResourcePool() + if err != nil { + return nil, errors.Wrap(err, "getResourcePool") + } + + kind := types.VirtualDiskTypeThin + + params := types.OvfCreateImportSpecParams{ + DiskProvisioning: string(kind), + EntityName: disk.ImportName, + } + + spec, err := m.CreateImportSpec(ctx, descriptor, pool, datastore, params) + if err != nil { + return nil, err + } + if spec.Error != nil { + return nil, errors.Error(spec.Error[0].LocalizedMessage) + } + + lease, err := pool.ImportVApp(ctx, spec.ImportSpec, folders.VmFolder, host.GetoHostSystem()) + if err != nil { + return nil, err + } + + info, err := lease.Wait(ctx, spec.FileItem) + if err != nil { + return nil, err + } + + f, err := os.Open(diskFile) + if err != nil { + return nil, err + } + + lr := newLeaseLogger("upload vmdk", 5) + + lr.Log() + defer lr.End() + + opts := soap.Upload{ + ContentLength: disk.Size, + Progress: lr, + } + + u := lease.StartUpdater(ctx, info) + defer u.Done() + + item := info.Items[0] // we only have 1 disk to upload + + err = lease.Upload(ctx, item, f, opts) + + _ = f.Close() + + if err != nil { + return nil, errors.Wrap(err, "lease.Upload") + } + + if err = lease.Complete(ctx); err != nil { + log.Debugf("lease complete error: %s, sleep 1s and try again", err) + time.Sleep(time.Second) + if err = lease.Complete(ctx); err != nil { + return nil, errors.Wrap(err, "lease.Complete") + } + } + + return object.NewVirtualMachine(c, info.Entity), nil +} diff --git a/pkg/multicloud/esxi/vdisk.go b/pkg/multicloud/esxi/vdisk.go index f5c171ae5b..d16a8775a8 100644 --- a/pkg/multicloud/esxi/vdisk.go +++ b/pkg/multicloud/esxi/vdisk.go @@ -352,9 +352,13 @@ func (disk *SVirtualDisk) GetExpiredAt() time.Time { } func (disk *SVirtualDisk) Rebuild(ctx context.Context) error { - return disk.vm.rebuildDisk(ctx, disk) + return disk.vm.rebuildDisk(ctx, disk, "") } func (disk *SVirtualDisk) GetProjectId() string { return "" } + +func (disk *SVirtualDisk) GetFilename() string { + return disk.getBackingInfo().GetFileName() +} diff --git a/pkg/multicloud/esxi/virtualmachine.go b/pkg/multicloud/esxi/virtualmachine.go index aa016c3b3a..029343d995 100644 --- a/pkg/multicloud/esxi/virtualmachine.go +++ b/pkg/multicloud/esxi/virtualmachine.go @@ -24,6 +24,7 @@ import ( "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" "yunion.io/x/jsonutils" @@ -36,8 +37,10 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + cloudtypes "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/netutils2" ) var VIRTUAL_MACHINE_PROPS = []string{"name", "parent", "runtime", "summary", "config", "guest", "resourcePool", "layoutEx"} @@ -160,7 +163,14 @@ func (self *SVirtualMachine) RebuildRoot(ctx context.Context, imageId string, pa return "", cloudprovider.ErrNotImplemented } -func (self *SVirtualMachine) rebuildDisk(ctx context.Context, disk *SVirtualDisk) error { +func (self *SVirtualMachine) DoRebuildRoot(ctx context.Context, imagePath string, uuid string) error { + if len(self.vdisks) == 0 { + return errors.ErrNotFound + } + return self.rebuildDisk(ctx, &self.vdisks[0], imagePath) +} + +func (self *SVirtualMachine) rebuildDisk(ctx context.Context, disk *SVirtualDisk, imagePath string) error { uuid := disk.GetId() sizeMb := disk.GetDiskSizeMB() index := disk.index @@ -171,7 +181,7 @@ func (self *SVirtualMachine) rebuildDisk(ctx context.Context, disk *SVirtualDisk if err != nil { return err } - return self.createDiskInternal(ctx, sizeMb, uuid, int32(index), diskKey, ctlKey) + return self.createDiskInternal(ctx, sizeMb, uuid, int32(index), diskKey, ctlKey, imagePath, false) } func (self *SVirtualMachine) UpdateVM(ctx context.Context, name string) error { @@ -826,11 +836,12 @@ func (self *SVirtualMachine) CreateDisk(ctx context.Context, sizeMb int, uuid st ctlKey += int32(index / 2) } - return self.createDiskInternal(ctx, sizeMb, uuid, int32(index), diskKey, ctlKey) + return self.createDiskInternal(ctx, sizeMb, uuid, int32(index), diskKey, ctlKey, "", true) } -func (self *SVirtualMachine) createDiskInternal(ctx context.Context, sizeMb int, uuid string, index int32, diskKey int32, ctlKey int32) error { - devSpec := NewDiskDev(int64(sizeMb), "", uuid, index, diskKey, ctlKey) +func (self *SVirtualMachine) createDiskInternal(ctx context.Context, sizeMb int, uuid string, index int32, + diskKey int32, ctlKey int32, imagePath string, check bool) error { + devSpec := NewDiskDev(int64(sizeMb), imagePath, uuid, index, diskKey, ctlKey) spec := addDevSpec(devSpec) spec.FileOperation = types.VirtualDeviceConfigSpecFileOperationCreate configSpec := types.VirtualMachineConfigSpec{} @@ -846,6 +857,9 @@ func (self *SVirtualMachine) createDiskInternal(ctx context.Context, sizeMb int, if err != nil { return err } + if !check { + return nil + } oldDiskCnt := len(self.vdisks) maxTries := 60 for tried := 0; tried < maxTries; tried += 1 { @@ -906,3 +920,161 @@ func (self *SVirtualMachine) CheckFileInfo(ctx context.Context) error { } return nil } + +func (self *SVirtualMachine) DoRename(ctx context.Context, name string) error { + task, err := self.getVmObj().Rename(ctx, name) + if err != nil { + return errors.Wrap(err, "object.VirtualMachine.Rename") + } + return task.Wait(ctx) +} + +func (self *SVirtualMachine) GetMoid() string { + return self.getVirtualMachine().Self.Value +} + +func (self *SVirtualMachine) GetToolsVersion() string { + return self.getVirtualMachine().Guest.ToolsVersion +} + +func (self *SVirtualMachine) DoCustomize(ctx context.Context, params jsonutils.JSONObject) error { + spec := new(types.CustomizationSpec) + + ipSettings := new(types.CustomizationGlobalIPSettings) + domain := "local" + if params.Contains("domain") { + domain, _ = params.GetString("domain") + } + ipSettings.DnsSuffixList = []string{domain} + + // deal nics + nics, _ := params.GetArray("nics") + serverNics := make([]cloudtypes.SServerNic, len(nics)) + for i := range nics { + var nicType cloudtypes.SServerNic + nics[i].Unmarshal(&nicType) + serverNics[i] = nicType + } + + // find dnsServerList + for i := range serverNics { + dnsList := netutils2.GetNicDns(&serverNics[i]) + if len(dnsList) != 0 { + ipSettings.DnsServerList = dnsList + } + } + spec.GlobalIPSettings = *ipSettings + + maps := make([]types.CustomizationAdapterMapping, 0, len(nics)) + for _, nic := range serverNics { + conf := types.CustomizationAdapterMapping{} + conf.MacAddress = nic.Mac + if len(conf.MacAddress) == 0 { + conf.MacAddress = "9e:46:27:21:a2:b2" + } + + conf.Adapter = types.CustomizationIPSettings{} + fixedIp := new(types.CustomizationFixedIp) + fixedIp.IpAddress = nic.Ip + if len(fixedIp.IpAddress) == 0 { + fixedIp.IpAddress = "10.168.26.23" + } + conf.Adapter.Ip = fixedIp + maskLen := nic.Masklen + if maskLen == 0 { + maskLen = 24 + } + mask := netutils2.Netlen2Mask(maskLen) + conf.Adapter.SubnetMask = mask + + if len(nic.Gateway) != 0 { + conf.Adapter.Gateway = []string{nic.Gateway} + } + dnsList := netutils2.GetNicDns(&nic) + if len(dnsList) != 0 { + conf.Adapter.DnsServerList = dnsList + dns := nic.Domain + if len(dns) == 0 { + dns = "local" + } + conf.Adapter.DnsDomain = dns + } + maps = append(maps, conf) + } + spec.NicSettingMap = maps + + var ( + osName = "Linux" + name = "yunionhost" + ) + if params.Contains("os_name") { + osName, _ = params.GetString("os_name") + } + if params.Contains("name") { + name, _ = params.GetString("name") + } + if osName == "Linux" { + linuxPrep := types.CustomizationLinuxPrep{ + HostName: &types.CustomizationFixedName{Name: name}, + Domain: domain, + TimeZone: "Asia/Shanghai", + } + spec.Identity = &linuxPrep + } else { + sysPrep := types.CustomizationSysprep{ + GuiUnattended: types.CustomizationGuiUnattended{ + TimeZone: 210, + AutoLogon: false, + }, + UserData: types.CustomizationUserData{ + FullName: "Administrator", + OrgName: "Yunion", + ProductId: "", + ComputerName: &types.CustomizationFixedName{ + Name: name, + }, + }, + Identification: types.CustomizationIdentification{}, + } + spec.Identity = &sysPrep + } + task, err := self.getVmObj().Customize(ctx, *spec) + if err != nil { + return errors.Wrap(err, "object.VirtualMachine.Customize") + } + return task.Wait(ctx) +} + +func (self *SVirtualMachine) ExportTemplate(ctx context.Context, idx int, diskPath string) error { + lease, err := self.getVmObj().Export(ctx) + if err != nil { + return errors.Wrap(err, "esxi.SVirtualMachine.DoExportTemplate") + } + info, err := lease.Wait(ctx, nil) + if err != nil { + return errors.Wrap(err, "lease.Wait") + } + + u := lease.StartUpdater(ctx, info) + defer u.Done() + + if idx >= len(info.Items) { + return errors.Error(fmt.Sprintf("No such Device whose index is %d", idx)) + } + + lr := newLeaseLogger("download vmdk", 5) + lr.Log() + defer lr.End() + log.Debugf("download to %s start...", diskPath) + err = lease.DownloadFile(ctx, diskPath, info.Items[idx], soap.Download{Progress: lr}) + if err != nil { + return errors.Wrap(err, "lease.DownloadFile") + } + + err = lease.Complete(ctx) + if err != nil { + return errors.Wrap(err, "lease.Complete") + } + log.Debugf("download to %s finish", diskPath) + return nil +} diff --git a/pkg/util/netutils2/netutils.go b/pkg/util/netutils2/netutils.go index 2f7bfe13ce..f93ab8153b 100644 --- a/pkg/util/netutils2/netutils.go +++ b/pkg/util/netutils2/netutils.go @@ -266,7 +266,7 @@ type SNetInterface struct { Mask net.IPMask Mac string - // Mtu int + Mtu int } var SECRET_PREFIX = "169.254" @@ -319,6 +319,8 @@ func (n *SNetInterface) fetchConfig(expectIp string) { return } + n.Mtu = inter.MTU + n.Mac = inter.HardwareAddr.String() addrs, err := inter.Addrs() if err == nil { @@ -337,15 +339,6 @@ func (n *SNetInterface) fetchConfig(expectIp string) { } } - // mtuStr, err := fileutils2.FileGetContents(fmt.Sprintf("/sys/class/net/%s/mtu", n.name)) - // if err != nil { - // log.Errorln("Fail to read MTU for %s: %s", n.name, err) - // } - - // n.Mtu, err = strconv.Atoi(mtuStr) - // if err != nil { - // log.Errorln("Fail to read MTU for %s: %s", n.name, err) - // } } func (n *SNetInterface) DisableGso() { diff --git a/vendor/github.com/vmware/govmomi/ovf/cim.go b/vendor/github.com/vmware/govmomi/ovf/cim.go new file mode 100644 index 0000000000..ce20bde19f --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/cim.go @@ -0,0 +1,78 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +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 ovf + +/* +Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_VirtualSystemSettingData.xsd +*/ + +type CIMVirtualSystemSettingData struct { + ElementName string `xml:"ElementName"` + InstanceID string `xml:"InstanceID"` + + AutomaticRecoveryAction *uint8 `xml:"AutomaticRecoveryAction"` + AutomaticShutdownAction *uint8 `xml:"AutomaticShutdownAction"` + AutomaticStartupAction *uint8 `xml:"AutomaticStartupAction"` + AutomaticStartupActionDelay *string `xml:"AutomaticStartupActionDelay>Interval"` + AutomaticStartupActionSequenceNumber *uint16 `xml:"AutomaticStartupActionSequenceNumber"` + Caption *string `xml:"Caption"` + ConfigurationDataRoot *string `xml:"ConfigurationDataRoot"` + ConfigurationFile *string `xml:"ConfigurationFile"` + ConfigurationID *string `xml:"ConfigurationID"` + CreationTime *string `xml:"CreationTime"` + Description *string `xml:"Description"` + LogDataRoot *string `xml:"LogDataRoot"` + Notes []string `xml:"Notes"` + RecoveryFile *string `xml:"RecoveryFile"` + SnapshotDataRoot *string `xml:"SnapshotDataRoot"` + SuspendDataRoot *string `xml:"SuspendDataRoot"` + SwapFileDataRoot *string `xml:"SwapFileDataRoot"` + VirtualSystemIdentifier *string `xml:"VirtualSystemIdentifier"` + VirtualSystemType *string `xml:"VirtualSystemType"` +} + +/* +Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_ResourceAllocationSettingData.xsd +*/ + +type CIMResourceAllocationSettingData struct { + ElementName string `xml:"ElementName"` + InstanceID string `xml:"InstanceID"` + + ResourceType *uint16 `xml:"ResourceType"` + OtherResourceType *string `xml:"OtherResourceType"` + ResourceSubType *string `xml:"ResourceSubType"` + + AddressOnParent *string `xml:"AddressOnParent"` + Address *string `xml:"Address"` + AllocationUnits *string `xml:"AllocationUnits"` + AutomaticAllocation *bool `xml:"AutomaticAllocation"` + AutomaticDeallocation *bool `xml:"AutomaticDeallocation"` + Caption *string `xml:"Caption"` + Connection []string `xml:"Connection"` + ConsumerVisibility *uint16 `xml:"ConsumerVisibility"` + Description *string `xml:"Description"` + HostResource []string `xml:"HostResource"` + Limit *uint64 `xml:"Limit"` + MappingBehavior *uint `xml:"MappingBehavior"` + Parent *string `xml:"Parent"` + PoolID *string `xml:"PoolID"` + Reservation *uint64 `xml:"Reservation"` + VirtualQuantity *uint `xml:"VirtualQuantity"` + VirtualQuantityUnits *string `xml:"VirtualQuantityUnits"` + Weight *uint `xml:"Weight"` +} diff --git a/vendor/github.com/vmware/govmomi/ovf/doc.go b/vendor/github.com/vmware/govmomi/ovf/doc.go new file mode 100644 index 0000000000..6284b1ac58 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/doc.go @@ -0,0 +1,25 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +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 ovf provides functionality to unmarshal and inspect the structure +of an OVF file. It is not a complete implementation of the specification and +is intended to be used to import virtual infrastructure into vSphere. + +For a complete specification of the OVF standard, refer to: +https://www.dmtf.org/sites/default/files/standards/documents/DSP0243_2.1.0.pdf +*/ +package ovf diff --git a/vendor/github.com/vmware/govmomi/ovf/env.go b/vendor/github.com/vmware/govmomi/ovf/env.go new file mode 100644 index 0000000000..584564ab77 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/env.go @@ -0,0 +1,99 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +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 ovf + +import ( + "bytes" + "fmt" + + "github.com/vmware/govmomi/vim25/xml" +) + +const ( + ovfEnvHeader = `` + ovfEnvPlatformSection = ` + %s + %s + %s + %s + ` + ovfEnvPropertyHeader = `` + ovfEnvPropertyEntry = `` + ovfEnvPropertyFooter = `` + ovfEnvFooter = `` +) + +type Env struct { + XMLName xml.Name `xml:"http://schemas.dmtf.org/ovf/environment/1 Environment"` + ID string `xml:"id,attr"` + EsxID string `xml:"http://www.vmware.com/schema/ovfenv esxId,attr"` + + Platform *PlatformSection `xml:"PlatformSection"` + Property *PropertySection `xml:"PropertySection"` +} + +type PlatformSection struct { + Kind string `xml:"Kind"` + Version string `xml:"Version"` + Vendor string `xml:"Vendor"` + Locale string `xml:"Locale"` +} + +type PropertySection struct { + Properties []EnvProperty `xml:"Property"` +} + +type EnvProperty struct { + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` +} + +// Marshal marshals Env to xml by using xml.Marshal. +func (e Env) Marshal() (string, error) { + x, err := xml.Marshal(e) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s%s", xml.Header, x), nil +} + +// MarshalManual manually marshals Env to xml suitable for a vApp guest. +// It exists to overcome the lack of expressiveness in Go's XML namespaces. +func (e Env) MarshalManual() string { + var buffer bytes.Buffer + + buffer.WriteString(xml.Header) + buffer.WriteString(fmt.Sprintf(ovfEnvHeader, e.EsxID)) + buffer.WriteString(fmt.Sprintf(ovfEnvPlatformSection, e.Platform.Kind, e.Platform.Version, e.Platform.Vendor, e.Platform.Locale)) + + buffer.WriteString(fmt.Sprintf(ovfEnvPropertyHeader)) + for _, p := range e.Property.Properties { + buffer.WriteString(fmt.Sprintf(ovfEnvPropertyEntry, p.Key, p.Value)) + } + buffer.WriteString(fmt.Sprintf(ovfEnvPropertyFooter)) + + buffer.WriteString(fmt.Sprintf(ovfEnvFooter)) + + return buffer.String() +} diff --git a/vendor/github.com/vmware/govmomi/ovf/envelope.go b/vendor/github.com/vmware/govmomi/ovf/envelope.go new file mode 100644 index 0000000000..d8b6fd8953 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/envelope.go @@ -0,0 +1,191 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +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 ovf + +type Envelope struct { + References []File `xml:"References>File"` + + // Package level meta-data + Annotation *AnnotationSection `xml:"AnnotationSection"` + Product *ProductSection `xml:"ProductSection"` + Network *NetworkSection `xml:"NetworkSection"` + Disk *DiskSection `xml:"DiskSection"` + OperatingSystem *OperatingSystemSection `xml:"OperatingSystemSection"` + Eula *EulaSection `xml:"EulaSection"` + VirtualHardware *VirtualHardwareSection `xml:"VirtualHardwareSection"` + ResourceAllocation *ResourceAllocationSection `xml:"ResourceAllocationSection"` + DeploymentOption *DeploymentOptionSection `xml:"DeploymentOptionSection"` + + // Content: A VirtualSystem or a VirtualSystemCollection + VirtualSystem *VirtualSystem `xml:"VirtualSystem"` +} + +type VirtualSystem struct { + Content + + Annotation []AnnotationSection `xml:"AnnotationSection"` + Product []ProductSection `xml:"ProductSection"` + OperatingSystem []OperatingSystemSection `xml:"OperatingSystemSection"` + Eula []EulaSection `xml:"EulaSection"` + VirtualHardware []VirtualHardwareSection `xml:"VirtualHardwareSection"` +} + +type File struct { + ID string `xml:"id,attr"` + Href string `xml:"href,attr"` + Size uint `xml:"size,attr"` + Compression *string `xml:"compression,attr"` + ChunkSize *int `xml:"chunkSize,attr"` +} + +type Content struct { + ID string `xml:"id,attr"` + Info string `xml:"Info"` + Name *string `xml:"Name"` +} + +type Section struct { + Required *bool `xml:"required,attr"` + Info string `xml:"Info"` +} + +type AnnotationSection struct { + Section + + Annotation string `xml:"Annotation"` +} + +type ProductSection struct { + Section + + Class *string `xml:"class,attr"` + Instance *string `xml:"instance,attr"` + + Product string `xml:"Product"` + Vendor string `xml:"Vendor"` + Version string `xml:"Version"` + FullVersion string `xml:"FullVersion"` + ProductURL string `xml:"ProductUrl"` + VendorURL string `xml:"VendorUrl"` + AppURL string `xml:"AppUrl"` + Property []Property `xml:"Property"` +} + +type Property struct { + Key string `xml:"key,attr"` + Type string `xml:"type,attr"` + Qualifiers *string `xml:"qualifiers,attr"` + UserConfigurable *bool `xml:"userConfigurable,attr"` + Default *string `xml:"value,attr"` + Password *bool `xml:"password,attr"` + + Label *string `xml:"Label"` + Description *string `xml:"Description"` + + Values []PropertyConfigurationValue `xml:"Value"` +} + +type PropertyConfigurationValue struct { + Value string `xml:"value,attr"` + Configuration *string `xml:"configuration,attr"` +} + +type NetworkSection struct { + Section + + Networks []Network `xml:"Network"` +} + +type Network struct { + Name string `xml:"name,attr"` + + Description string `xml:"Description"` +} + +type DiskSection struct { + Section + + Disks []VirtualDiskDesc `xml:"Disk"` +} + +type VirtualDiskDesc struct { + DiskID string `xml:"diskId,attr"` + FileRef *string `xml:"fileRef,attr"` + Capacity string `xml:"capacity,attr"` + CapacityAllocationUnits *string `xml:"capacityAllocationUnits,attr"` + Format *string `xml:"format,attr"` + PopulatedSize *int `xml:"populatedSize,attr"` + ParentRef *string `xml:"parentRef,attr"` +} + +type OperatingSystemSection struct { + Section + + ID int16 `xml:"id,attr"` + Version *string `xml:"version,attr"` + OSType *string `xml:"osType,attr"` + + Description *string `xml:"Description"` +} + +type EulaSection struct { + Section + + License string `xml:"License"` +} + +type VirtualHardwareSection struct { + Section + + ID *string `xml:"id,attr"` + Transport *string `xml:"transport,attr"` + + System *VirtualSystemSettingData `xml:"System"` + Item []ResourceAllocationSettingData `xml:"Item"` +} + +type VirtualSystemSettingData struct { + CIMVirtualSystemSettingData +} + +type ResourceAllocationSettingData struct { + CIMResourceAllocationSettingData + + Required *bool `xml:"required,attr"` + Configuration *string `xml:"configuration,attr"` + Bound *string `xml:"bound,attr"` +} + +type ResourceAllocationSection struct { + Section + + Item []ResourceAllocationSettingData `xml:"Item"` +} + +type DeploymentOptionSection struct { + Section + + Configuration []DeploymentOptionConfiguration `xml:"Configuration"` +} + +type DeploymentOptionConfiguration struct { + ID string `xml:"id,attr"` + Default *bool `xml:"default,attr"` + + Label string `xml:"Label"` + Description string `xml:"Description"` +} diff --git a/vendor/github.com/vmware/govmomi/ovf/manager.go b/vendor/github.com/vmware/govmomi/ovf/manager.go new file mode 100644 index 0000000000..3ee2afdd46 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/manager.go @@ -0,0 +1,103 @@ +/* +Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved. + +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 ovf + +import ( + "context" + + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type Manager struct { + types.ManagedObjectReference + + c *vim25.Client +} + +func NewManager(c *vim25.Client) *Manager { + return &Manager{*c.ServiceContent.OvfManager, c} +} + +// CreateDescriptor wraps methods.CreateDescriptor +func (m *Manager) CreateDescriptor(ctx context.Context, obj mo.Reference, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) { + req := types.CreateDescriptor{ + This: m.Reference(), + Obj: obj.Reference(), + Cdp: cdp, + } + + res, err := methods.CreateDescriptor(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} + +// CreateImportSpec wraps methods.CreateImportSpec +func (m *Manager) CreateImportSpec(ctx context.Context, ovfDescriptor string, resourcePool mo.Reference, datastore mo.Reference, cisp types.OvfCreateImportSpecParams) (*types.OvfCreateImportSpecResult, error) { + req := types.CreateImportSpec{ + This: m.Reference(), + OvfDescriptor: ovfDescriptor, + ResourcePool: resourcePool.Reference(), + Datastore: datastore.Reference(), + Cisp: cisp, + } + + res, err := methods.CreateImportSpec(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} + +// ParseDescriptor wraps methods.ParseDescriptor +func (m *Manager) ParseDescriptor(ctx context.Context, ovfDescriptor string, pdp types.OvfParseDescriptorParams) (*types.OvfParseDescriptorResult, error) { + req := types.ParseDescriptor{ + This: m.Reference(), + OvfDescriptor: ovfDescriptor, + Pdp: pdp, + } + + res, err := methods.ParseDescriptor(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} + +// ValidateHost wraps methods.ValidateHost +func (m *Manager) ValidateHost(ctx context.Context, ovfDescriptor string, host mo.Reference, vhp types.OvfValidateHostParams) (*types.OvfValidateHostResult, error) { + req := types.ValidateHost{ + This: m.Reference(), + OvfDescriptor: ovfDescriptor, + Host: host.Reference(), + Vhp: vhp, + } + + res, err := methods.ValidateHost(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} diff --git a/vendor/github.com/vmware/govmomi/ovf/ovf.go b/vendor/github.com/vmware/govmomi/ovf/ovf.go new file mode 100644 index 0000000000..bd279e757d --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/ovf.go @@ -0,0 +1,35 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +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 ovf + +import ( + "io" + + "github.com/vmware/govmomi/vim25/xml" +) + +func Unmarshal(r io.Reader) (*Envelope, error) { + var e Envelope + + dec := xml.NewDecoder(r) + err := dec.Decode(&e) + if err != nil { + return nil, err + } + + return &e, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ff50bff2ec..eb4ae34ee5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -507,6 +507,7 @@ github.com/vmihailenco/msgpack/codes github.com/vmware/govmomi github.com/vmware/govmomi/nfc github.com/vmware/govmomi/object +github.com/vmware/govmomi/ovf github.com/vmware/govmomi/property github.com/vmware/govmomi/session github.com/vmware/govmomi/task