diff --git a/build/baremetal-agent/vars b/build/baremetal-agent/vars
index 02523c8901..70ad3321bd 100644
--- a/build/baremetal-agent/vars
+++ b/build/baremetal-agent/vars
@@ -1 +1,6 @@
-DESCRIPTION="Baremetal Agent Command Line Utility"
\ No newline at end of file
+DESCRIPTION="Baremetal Agent Command Line Utility"
+
+REQUIRES=(
+ ipmitool
+ mkisofs
+)
diff --git a/cmd/climc/shell/baremetalagents.go b/cmd/climc/shell/baremetalagents.go
index 6483574841..63b57fa94e 100644
--- a/cmd/climc/shell/baremetalagents.go
+++ b/cmd/climc/shell/baremetalagents.go
@@ -47,6 +47,15 @@ func init() {
type BaremetalAgentOpsOperations struct {
ID string `help:"ID or name of agent"`
}
+ R(&BaremetalAgentOpsOperations{}, "agent-show", "Show details of an agent", func(s *mcclient.ClientSession, args *BaremetalAgentOpsOperations) error {
+ result, err := modules.Baremetalagents.Get(s, args.ID, nil)
+ if err != nil {
+ return err
+ }
+ printObject(result)
+ return nil
+ })
+
R(&BaremetalAgentOpsOperations{}, "agent-enable", "Enable agent", func(s *mcclient.ClientSession, args *BaremetalAgentOpsOperations) error {
result, err := modules.Baremetalagents.PerformAction(s, args.ID, "enable", nil)
if err != nil {
diff --git a/cmd/climc/shell/hosts.go b/cmd/climc/shell/hosts.go
index af950c5182..0fb2739431 100644
--- a/cmd/climc/shell/hosts.go
+++ b/cmd/climc/shell/hosts.go
@@ -22,6 +22,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
+ "yunion.io/x/onecloud/pkg/util/fileutils2"
)
func init() {
@@ -133,6 +134,12 @@ func init() {
return nil
})
+ R(&HostOpsOptions{}, "host-ipmi-probe", "Do ipmi probe host", func(s *mcclient.ClientSession, args *HostOpsOptions) error {
+ results := modules.Hosts.BatchPerformAction(s, args.ID, "ipmi-probe", nil)
+ printBatchResults(results, modules.Hosts.GetColumns(s))
+ return nil
+ })
+
R(&HostDetailOptions{}, "host-ipmi", "Get IPMI information of a host", func(s *mcclient.ClientSession, args *HostDetailOptions) error {
result, err := modules.Hosts.GetSpecific(s, args.ID, "ipmi", nil)
if err != nil {
@@ -185,7 +192,8 @@ func init() {
MemoryReserved string `help:"Memory reserved"`
CpuReserved int64 `help:"CPU reserved"`
HostType string `help:"Change host type, CAUTION!!!!" choices:"hypervisor|kubelet|esxi|baremetal"`
- AccessIp string `help:"Change access ip, CAUTION!!!!"`
+ // AccessIp string `help:"Change access ip, CAUTION!!!!"`
+ AccessMac string `help:"Change access MAC, CAUTION!!!!"`
}
R(&HostUpdateOptions{}, "host-update", "Update information of a host", func(s *mcclient.ClientSession, args *HostUpdateOptions) error {
params := jsonutils.NewDict()
@@ -210,8 +218,8 @@ func init() {
if len(args.HostType) > 0 {
params.Add(jsonutils.NewString(args.HostType), "host_type")
}
- if len(args.AccessIp) > 0 {
- params.Add(jsonutils.NewString(args.AccessIp), "access_ip")
+ if len(args.AccessMac) > 0 {
+ params.Add(jsonutils.NewString(args.AccessMac), "access_mac")
}
if params.Size() == 0 {
return fmt.Errorf("Not data to update")
@@ -472,6 +480,14 @@ func init() {
IpmiUser string `help:"IPMI user name"`
IpmiPasswd string `help:"IPMI user password"`
IpmiAddr string `help:"IPMI IP address"`
+
+ AccessIp string `help:"Access IP address"`
+ AccessNet string `help:"Access network"`
+ AccessWire string `help:"Access wire"`
+
+ NoPrepare bool `help:"just initialize, do not reboot baremetal to prepare"`
+
+ DisablePxeBoot bool `help:"set enable_pxe_boot to false, which is true by default"`
}
R(&HostCreateOptions{}, "host-create", "Create a baremetal host", func(s *mcclient.ClientSession, args *HostCreateOptions) error {
params := jsonutils.NewDict()
@@ -493,6 +509,21 @@ func init() {
if len(args.IpmiAddr) > 0 {
params.Add(jsonutils.NewString(args.IpmiAddr), "ipmi_ip_addr")
}
+ if len(args.AccessIp) > 0 {
+ params.Add(jsonutils.NewString(args.AccessIp), "access_ip")
+ }
+ if len(args.AccessNet) > 0 {
+ params.Add(jsonutils.NewString(args.AccessNet), "access_net")
+ }
+ if len(args.AccessWire) > 0 {
+ params.Add(jsonutils.NewString(args.AccessWire), "access_wire")
+ }
+ if args.NoPrepare {
+ params.Add(jsonutils.JSONTrue, "no_prepare")
+ }
+ if args.DisablePxeBoot {
+ params.Add(jsonutils.JSONFalse, "enable_pxe_boot")
+ }
result, err := modules.Hosts.Create(s, params)
if err != nil {
return err
@@ -540,4 +571,25 @@ func init() {
printObject(spec)
return nil
})
+
+ type HostJnlpOptions struct {
+ ID string `help:"ID or name of host"`
+ Save string `help:"save xml into this file"`
+ }
+ R(&HostJnlpOptions{}, "host-jnlp", "Get host jnlp file contentn", func(s *mcclient.ClientSession, args *HostJnlpOptions) error {
+ spec, err := modules.Hosts.GetSpecific(s, args.ID, "jnlp", nil)
+ if err != nil {
+ return err
+ }
+ jnlp, err := spec.GetString("jnlp")
+ if err != nil {
+ return err
+ }
+ if len(args.Save) > 0 {
+ return fileutils2.FilePutContents(args.Save, jnlp, false)
+ } else {
+ fmt.Println(jnlp)
+ }
+ return nil
+ })
}
diff --git a/cmd/climc/shell/reservedips.go b/cmd/climc/shell/reservedips.go
index 64529d9e3f..d80550c6fc 100644
--- a/cmd/climc/shell/reservedips.go
+++ b/cmd/climc/shell/reservedips.go
@@ -24,14 +24,18 @@ import (
func init() {
type NetworkReserveIPOptions struct {
- NETWORK string `help:"IP or name of network"`
- NOTES string `help:"Why reserve this IP"`
- IPS []string `help:"IPs to reserve"`
+ NETWORK string `help:"IP or name of network"`
+ NOTES string `help:"Why reserve this IP"`
+ IPS []string `help:"IPs to reserve"`
+ Duration string `help:"reservation duration, e.g. 1I, 1H, 2M"`
}
R(&NetworkReserveIPOptions{}, "network-reserve-ip", "Reserve an IP address from pool", func(s *mcclient.ClientSession, args *NetworkReserveIPOptions) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewStringArray(args.IPS), "ips")
params.Add(jsonutils.NewString(args.NOTES), "notes")
+ if len(args.Duration) > 0 {
+ params.Add(jsonutils.NewString(args.Duration), "duration")
+ }
net, err := modules.Networks.PerformAction(s, args.NETWORK, "reserve-ip", params)
if err != nil {
return err
@@ -58,6 +62,7 @@ func init() {
type ReservedIPListOptions struct {
options.BaseListOptions
Network string `help:"Network filter"`
+ All bool `help:"show expired reserved ips"`
}
R(&ReservedIPListOptions{}, "reserved-ip-list", "Show all reserved IPs for any network", func(s *mcclient.ClientSession, args *ReservedIPListOptions) error {
var params *jsonutils.JSONDict
@@ -72,6 +77,9 @@ func init() {
if len(args.Network) > 0 {
params.Add(jsonutils.NewString(args.Network), "network")
}
+ if args.All {
+ params.Add(jsonutils.JSONTrue, "all")
+ }
result, err := modules.ReservedIPs.List(s, params)
if err != nil {
return err
diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go
index 896a1461fe..88266faa55 100644
--- a/cmd/climc/shell/servers.go
+++ b/cmd/climc/shell/servers.go
@@ -32,6 +32,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
+ "yunion.io/x/onecloud/pkg/util/fileutils2"
)
func init() {
@@ -1147,4 +1148,25 @@ func init() {
printObject(result)
return nil
})
+
+ type ServerJnlpOptions struct {
+ ID string `help:"ID or name of server"`
+ Save string `help:"save xml into this file"`
+ }
+ R(&ServerJnlpOptions{}, "server-jnlp", "Get baremetal server jnlp file contentn", func(s *mcclient.ClientSession, args *ServerJnlpOptions) error {
+ spec, err := modules.Servers.GetSpecific(s, args.ID, "jnlp", nil)
+ if err != nil {
+ return err
+ }
+ jnlp, err := spec.GetString("jnlp")
+ if err != nil {
+ return err
+ }
+ if len(args.Save) > 0 {
+ return fileutils2.FilePutContents(args.Save, jnlp, false)
+ } else {
+ fmt.Println(jnlp)
+ }
+ return nil
+ })
}
diff --git a/cmd/redfishcli/shell/chassis.go b/cmd/redfishcli/shell/chassis.go
index e461aff2c4..ab135915f1 100644
--- a/cmd/redfishcli/shell/chassis.go
+++ b/cmd/redfishcli/shell/chassis.go
@@ -1,3 +1,17 @@
+// 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 shell
import (
diff --git a/cmd/redfishcli/shell/manager.go b/cmd/redfishcli/shell/manager.go
index 5865a47fe6..2c32b4b51b 100644
--- a/cmd/redfishcli/shell/manager.go
+++ b/cmd/redfishcli/shell/manager.go
@@ -1,3 +1,17 @@
+// 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 shell
import (
@@ -14,6 +28,17 @@ import (
func init() {
+ type LanConfigGetOptions struct {
+ }
+ shellutils.R(&LanConfigGetOptions{}, "lan-get", "Get configuration of BMC lan", func(cli redfish.IRedfishDriver, args *LanConfigGetOptions) error {
+ confs, err := cli.GetLanConfigs(context.Background())
+ if err != nil {
+ return err
+ }
+ printutils.PrintInterfaceList(confs, 0, 0, 0, nil)
+ return nil
+ })
+
type VirtualMediaGetOptions struct {
}
shellutils.R(&VirtualMediaGetOptions{}, "cdrom-get", "Get details of manager virtual media", func(cli redfish.IRedfishDriver, args *VirtualMediaGetOptions) error {
@@ -28,21 +53,12 @@ func init() {
})
type VirtualMediaMountOptions struct {
- URL string `help:"cdrom http URL"`
+ URL string `help:"cdrom http URL"`
+ Boot bool `help:"set boot from virtualmedia on next boot"`
}
shellutils.R(&VirtualMediaMountOptions{}, "cdrom-insert", "Insert iso into virtual CD-ROM", func(cli redfish.IRedfishDriver, args *VirtualMediaMountOptions) error {
ctx := context.Background()
- path, cdInfo, err := cli.GetVirtualCdromInfo(ctx)
- if err != nil {
- return err
- }
- if len(cdInfo.Image) > 0 {
- return fmt.Errorf("image %s in cd-rom", cdInfo.Image)
- }
- if !cdInfo.SupportAction {
- return fmt.Errorf("action not supported")
- }
- err = cli.MountVirtualCdrom(context.Background(), path, args.URL)
+ err := redfish.MountVirtualCdrom(ctx, cli, args.URL, args.Boot)
if err != nil {
return err
}
@@ -54,17 +70,7 @@ func init() {
}
shellutils.R(&VirtualMediaUmountOptions{}, "cdrom-eject", "Eject iso from virtual CD-ROM", func(cli redfish.IRedfishDriver, args *VirtualMediaUmountOptions) error {
ctx := context.Background()
- path, cdInfo, err := cli.GetVirtualCdromInfo(ctx)
- if err != nil {
- return err
- }
- if len(cdInfo.Image) == 0 {
- return fmt.Errorf("no image in cd-rom")
- }
- if !cdInfo.SupportAction {
- return fmt.Errorf("action not supported")
- }
- err = cli.UmountVirtualCdrom(context.Background(), path)
+ err := redfish.UmountVirtualCdrom(ctx, cli)
if err != nil {
return err
}
diff --git a/cmd/redfishcli/shell/shell.go b/cmd/redfishcli/shell/shell.go
index 6b16993cb0..969c8571af 100644
--- a/cmd/redfishcli/shell/shell.go
+++ b/cmd/redfishcli/shell/shell.go
@@ -1,3 +1,17 @@
+// 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 shell
import (
diff --git a/cmd/redfishcli/shell/system.go b/cmd/redfishcli/shell/system.go
index e328e231c0..84b00f4381 100644
--- a/cmd/redfishcli/shell/system.go
+++ b/cmd/redfishcli/shell/system.go
@@ -1,3 +1,17 @@
+// 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 shell
import (
@@ -37,12 +51,7 @@ func init() {
DEV string `help:"next boot device"`
}
shellutils.R(&SetNextBootOptions{}, "set-next-boot-dev", "Set next boot device", func(cli redfish.IRedfishDriver, args *SetNextBootOptions) error {
- var err error
- if args.DEV == "vcd" {
- err = cli.SetNextBootVirtualCdrom(context.Background())
- } else {
- err = cli.SetNextBootDev(context.Background(), args.DEV)
- }
+ err := cli.SetNextBootDev(context.Background(), args.DEV)
if err != nil {
return err
}
diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go
index 9168f6b68d..f43e1a29e5 100644
--- a/pkg/apis/compute/host_const.go
+++ b/pkg/apis/compute/host_const.go
@@ -63,6 +63,10 @@ const (
BAREMETAL_START_FAIL = "start_fail"
BAREMETAL_STOP_FAIL = "stop_fail"
+ BAREMETAL_START_PROBE = "start_probe"
+ BAREMETAL_PROBING = "probing"
+ BAREMETAL_PROBE_FAIL = "probe_fail"
+
HOST_STATUS_RUNNING = BAREMETAL_RUNNING
HOST_STATUS_READY = BAREMETAL_READY
HOST_STATUS_UNKNOWN = BAREMETAL_UNKNOWN
@@ -96,3 +100,7 @@ var HOST_TYPES = []string{
}
var NIC_TYPES = []string{NIC_TYPE_IPMI, NIC_TYPE_ADMIN}
+
+const (
+ ACCESS_MAC_ANY = "00:00:00:00:00:00"
+)
diff --git a/pkg/apis/compute/network_const.go b/pkg/apis/compute/network_const.go
index e40c7fa41a..158a584338 100644
--- a/pkg/apis/compute/network_const.go
+++ b/pkg/apis/compute/network_const.go
@@ -56,3 +56,13 @@ var (
CLOUD_PROVIDER_UCLOUD,
}
)
+
+type IPAllocationDirection string
+
+const (
+ IPAllocationStepdown IPAllocationDirection = "stepdown"
+ IPAllocationStepup IPAllocationDirection = "stepup"
+ IPAllocationRadnom IPAllocationDirection = "random"
+ IPAllocationNone IPAllocationDirection = "none"
+ IPAllocationDefault = ""
+)
diff --git a/pkg/baremetal/agent.go b/pkg/baremetal/agent.go
index 0789c88df1..214886f8ed 100644
--- a/pkg/baremetal/agent.go
+++ b/pkg/baremetal/agent.go
@@ -18,15 +18,18 @@ import (
"context"
"fmt"
"net"
+ "net/http"
"yunion.io/x/log"
+ "yunion.io/x/onecloud/pkg/appsrv"
o "yunion.io/x/onecloud/pkg/baremetal/options"
"yunion.io/x/onecloud/pkg/baremetal/pxe"
"yunion.io/x/onecloud/pkg/cloudcommon/agent"
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
+ "yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/procutils"
)
@@ -52,7 +55,7 @@ type SBaremetalAgent struct {
func newBaremetalAgent() (*SBaremetalAgent, error) {
agent := &SBaremetalAgent{}
- err := agent.Init(agent, o.Options.ListenInterface)
+ err := agent.Init(agent, o.Options.ListenInterface, o.Options.CachePath)
if err != nil {
return nil, err
}
@@ -131,7 +134,9 @@ func (agent *SBaremetalAgent) StartService() error {
}
agent.Manager = manager
+
agent.startPXEServices(manager)
+ agent.startFileServer()
agent.DoOnline(agent.GetAdminSession())
return nil
@@ -180,7 +185,25 @@ func (agent *SBaremetalAgent) startPXEServices(manager *SBaremetalManager) {
}()
}
-func Start() error {
+func (agent *SBaremetalAgent) startFileServer() {
+ dhcpListenIp, err := agent.GetDHCPServerListenIP()
+ if err != nil {
+ log.Fatalf("Get dhcp listen ip address error: %v", err)
+ }
+ fs := http.FileServer(httputils.Dir(o.Options.TftpRoot))
+ http.Handle("/tftp/", http.StripPrefix("/tftp/", fs))
+ cacheFs := http.FileServer(httputils.Dir(o.Options.CachePath))
+ http.Handle("/images/", http.StripPrefix("/images/", cacheFs))
+ isoFs := http.FileServer(httputils.Dir(o.Options.BootIsoPath))
+ http.Handle("/bootiso/", http.StripPrefix("/bootiso/", isoFs))
+ go func() {
+ if err := http.ListenAndServe(fmt.Sprintf("%s:%d", dhcpListenIp, o.Options.Port+1000), nil); err != nil {
+ panic(fmt.Sprintf("start http file server: %v", err))
+ }
+ }()
+}
+
+func Start(app *appsrv.Application) error {
var err error
if baremetalAgent != nil {
log.Warningf("Global baremetalAgent already start")
@@ -190,7 +213,12 @@ func Start() error {
if err != nil {
return err
}
- return baremetalAgent.Start()
+ err = baremetalAgent.Start()
+ if err != nil {
+ return err
+ }
+ baremetalAgent.AddImageCacheHandler("", app)
+ return nil
}
func Stop() error {
diff --git a/pkg/baremetal/handler/handlers.go b/pkg/baremetal/handler/handlers.go
index abf552bceb..e24a98d306 100644
--- a/pkg/baremetal/handler/handlers.go
+++ b/pkg/baremetal/handler/handlers.go
@@ -63,12 +63,13 @@ func initBaremetalsHandler(app *appsrv.Application) {
AddHandler(app, "GET", bmActionPrefix("notify"), bmObjMiddleware(handleBaremetalNotify))
AddHandler(app, "POST", bmActionPrefix("maintenance"), bmObjMiddleware(handleBaremetalMaintenance))
AddHandler(app, "POST", bmActionPrefix("unmaintenance"), bmObjMiddleware(handleBaremetalUnmaintenance))
- AddHandler(app, "POST", bmActionPrefix("delete"), bmObjMiddleware(handleBaremetalDelete))
+ AddHandler(app, "POST", bmActionPrefix("delete"), bmObjMiddlewareWithFetch(handleBaremetalDelete, false))
AddHandler(app, "POST", bmActionPrefix("syncstatus"), bmObjMiddleware(handleBaremetalSyncStatus))
AddHandler(app, "POST", bmActionPrefix("sync-config"), bmObjMiddleware(handleBaremetalSyncConfig))
AddHandler(app, "POST", bmActionPrefix("sync-ipmi"), bmObjMiddleware(handleBaremetalSyncIPMI))
AddHandler(app, "POST", bmActionPrefix("prepare"), bmObjMiddleware(handleBaremetalPrepare))
AddHandler(app, "POST", bmActionPrefix("reset-bmc"), bmObjMiddleware(handleBaremetalResetBMC))
+ AddHandler(app, "POST", bmActionPrefix("ipmi-probe"), bmObjMiddleware(handleBaremetalIpmiProbe))
// server actions handler
AddHandler(app, "POST", srvActionPrefix("create"), srvClassMiddleware(handleServerCreate))
@@ -148,6 +149,11 @@ func handleBaremetalResetBMC(ctx *Context, bm *baremetal.SBaremetalInstance) {
ctx.ResponseOk()
}
+func handleBaremetalIpmiProbe(ctx *Context, bm *baremetal.SBaremetalInstance) {
+ bm.StartBaremetalIpmiProbeTask(ctx.UserCred(), ctx.TaskId(), ctx.Data())
+ ctx.ResponseOk()
+}
+
func handleServerCreate(ctx *Context, bm *baremetal.SBaremetalInstance) {
err := bm.StartServerCreateTask(ctx.UserCred(), ctx.TaskId(), ctx.Data())
if err != nil {
diff --git a/pkg/baremetal/handler/middleware.go b/pkg/baremetal/handler/middleware.go
index 05b2f3160f..c89e97f69e 100644
--- a/pkg/baremetal/handler/middleware.go
+++ b/pkg/baremetal/handler/middleware.go
@@ -78,13 +78,26 @@ func authMiddleware(h handlerFunc) appsrv.FilterHandler {
type bmObjHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance)
func bmObjMiddleware(h bmObjHandlerFunc) appsrv.FilterHandler {
+ return bmObjMiddlewareWithFetch(h, true)
+}
+
+func bmObjMiddlewareWithFetch(h bmObjHandlerFunc, fetch bool) appsrv.FilterHandler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
newCtx := NewContext(ctx, w, r)
bmId := newCtx.Params()[PARAMS_BMID_KEY]
baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId)
if baremetal == nil {
- newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId))
- return
+ if fetch {
+ err := newCtx.GetBaremetalManager().InitBaremetal(bmId, false)
+ if err != nil {
+ newCtx.ResponseError(err)
+ return
+ }
+ baremetal = newCtx.GetBaremetalManager().GetBaremetalById(bmId)
+ } else {
+ newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId))
+ return
+ }
}
h(newCtx, baremetal)
}
diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go
index 98955f5c31..2d50e29026 100644
--- a/pkg/baremetal/manager.go
+++ b/pkg/baremetal/manager.go
@@ -28,11 +28,10 @@ import (
"sync"
"time"
- "github.com/pkg/errors"
-
"yunion.io/x/jsonutils"
"yunion.io/x/log"
- yerrors "yunion.io/x/pkg/util/errors"
+ "yunion.io/x/pkg/errors"
+ "yunion.io/x/pkg/util/netutils"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/pkg/util/seclib"
"yunion.io/x/pkg/util/sets"
@@ -61,7 +60,9 @@ import (
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/dhcp"
+ "yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
+ "yunion.io/x/onecloud/pkg/util/redfish"
"yunion.io/x/onecloud/pkg/util/ssh"
"yunion.io/x/onecloud/pkg/util/sysutils"
)
@@ -110,11 +111,10 @@ func (m *SBaremetalManager) loadConfigs() error {
}
}
- session := m.GetClientSession()
errsChannel := make(chan error, len(bmIds))
initBaremetal := func(i int) {
bmId := bmIds[i]
- err := m.initBaremetal(session, bmId)
+ err := m.InitBaremetal(bmId, true)
if err != nil {
errsChannel <- err
return
@@ -128,21 +128,34 @@ func (m *SBaremetalManager) loadConfigs() error {
errs = append(errs, <-errsChannel)
}
}
- return yerrors.NewAggregate(errs)
+ return errors.NewAggregate(errs)
}
-func (m *SBaremetalManager) initBaremetal(session *mcclient.ClientSession, bmId string) error {
- desc, err := m.updateBaremetal(session, bmId)
+func (m *SBaremetalManager) InitBaremetal(bmId string, update bool) error {
+ session := m.GetClientSession()
+ var err error
+ var desc jsonutils.JSONObject
+ if update {
+ desc, err = m.updateBaremetal(session, bmId)
+ } else {
+ desc, err = m.fetchBaremetal(session, bmId)
+ }
if err != nil {
return err
}
+ isBaremetal, _ := desc.Bool("is_baremetal")
+ if !isBaremetal {
+ return errors.Error("not a baremetal???")
+ }
bmInstance, err := m.AddBaremetal(desc)
if err != nil {
return err
}
- bmObj := bmInstance.(*SBaremetalInstance)
- if !sets.NewString(INIT, PREPARE, UNKNOWN).Has(bmObj.GetStatus()) {
- bmObj.SyncStatusBackground()
+ if update {
+ bmObj := bmInstance.(*SBaremetalInstance)
+ if !sets.NewString(INIT, PREPARE, UNKNOWN).Has(bmObj.GetStatus()) {
+ bmObj.SyncStatusBackground()
+ }
}
return nil
}
@@ -152,6 +165,7 @@ func (m *SBaremetalManager) CleanBaremetal(bmId string) {
if bm != nil {
bm.Stop()
}
+ bm.clearBootIsoImage()
path := bm.GetDir()
procutils.NewCommand("rm", "-fr", path).Run()
}
@@ -167,6 +181,15 @@ func (m *SBaremetalManager) updateBaremetal(session *mcclient.ClientSession, bmI
return obj, nil
}
+func (m *SBaremetalManager) fetchBaremetal(session *mcclient.ClientSession, bmId string) (jsonutils.JSONObject, error) {
+ obj, err := modules.Hosts.Get(session, bmId, nil)
+ if err != nil {
+ return nil, err
+ }
+ log.Infof("Baremetal %s update success", bmId)
+ return obj, nil
+}
+
func (m *SBaremetalManager) AddBaremetal(desc jsonutils.JSONObject) (pxe.IBaremetalInstance, error) {
id, err := desc.GetString("id")
if err != nil {
@@ -575,6 +598,7 @@ func (b *SBaremetalInstance) SaveSSHConfig(remoteAddr string, key string) error
return err
}
b.SyncSSHConfig(sshConf)
+ b.clearBootIso()
return err
}
@@ -926,13 +950,63 @@ func (b *SBaremetalInstance) getTftpFileUrl(filename string) string {
log.Errorf("Get http file server: %v", err)
return filename
}
- if o.Options.EnableTftpHttpDownload {
- return fmt.Sprintf("http://%s:%d/tftp/%s", serverIP, o.Options.Port+1000, filename)
+ return fmt.Sprintf("http://%s:%d/tftp/%s", serverIP, o.Options.Port+1000, filename)
+}
+
+func (b *SBaremetalInstance) getImageCacheUrl() string {
+ serverIP, err := b.manager.Agent.GetDHCPServerIP()
+ if err != nil {
+ log.Errorf("Get http file server: %v", err)
+ return ""
}
- return filename
+ // no /images/, rootcreate.sh will add this
+ return fmt.Sprintf("http://%s:%d", serverIP, o.Options.Port+1000)
+}
+
+func (b *SBaremetalInstance) getBootIsoUrl() string {
+ serverIP, err := b.manager.Agent.GetDHCPServerIP()
+ if err != nil {
+ log.Errorf("Get http file server: %v", err)
+ return ""
+ }
+ // no /images/, rootcreate.sh will add this
+ return fmt.Sprintf("http://%s:%d/bootiso/%s.iso", serverIP, o.Options.Port+1000, b.GetId())
}
func (b *SBaremetalInstance) GetTFTPResponse() string {
+ return b.getSyslinuxConf(true)
+}
+
+func (b *SBaremetalInstance) getIsolinuxConf() string {
+ return b.getSyslinuxConf(false)
+}
+
+func (b *SBaremetalInstance) getSyslinuxPath(filename string, isTftp bool) string {
+ if isTftp {
+ return b.getTftpFileUrl(filename)
+ } else {
+ return filename
+ }
+}
+
+func (b *SBaremetalInstance) findAccessNetwork(accessIp string) (*types.SNetworkConfig, error) {
+ params := jsonutils.NewDict()
+ params.Add(jsonutils.NewString(accessIp), "ip")
+ params.Add(jsonutils.JSONTrue, "is_on_premise")
+ session := b.manager.GetClientSession()
+ ret, err := modules.Networks.List(session, params)
+ if err != nil {
+ return nil, err
+ }
+ if len(ret.Data) == 0 {
+ return nil, errors.Wrapf(httperrors.ErrNotFound, "accessIp %s", accessIp)
+ }
+ network := types.SNetworkConfig{}
+ err = ret.Data[0].Unmarshal(&network)
+ return &network, err
+}
+
+func (b *SBaremetalInstance) getSyslinuxConf(isTftp bool) string {
resp := `DEFAULT start
serial 1 115200
@@ -942,15 +1016,45 @@ LABEL start
`
if b.NeedPXEBoot() {
- resp += fmt.Sprintf(" kernel %s\n", b.getTftpFileUrl("kernel"))
+ kernel := "vmlinuz"
+ initramfs := "initrd.img"
+ if isTftp {
+ kernel = b.getTftpFileUrl("kernel")
+ initramfs = b.getTftpFileUrl("initramfs")
+ }
+ resp += fmt.Sprintf(" kernel %s\n", kernel)
args := []string{
- fmt.Sprintf("initrd=%s", b.getTftpFileUrl("initramfs")),
+ fmt.Sprintf("initrd=%s", initramfs),
fmt.Sprintf("token=%s", auth.GetTokenString()),
fmt.Sprintf("url=%s", b.GetNotifyUrl()),
}
+ if !isTftp {
+ adminNic := b.GetAdminNic()
+ var addr string
+ var mask string
+ var gateway string
+ if adminNic != nil {
+ addr = adminNic.IpAddr
+ mask = adminNic.GetNetMask()
+ gateway = adminNic.Gateway
+ } else {
+ accessIp := b.GetAccessIp()
+ accessNet, _ := b.findAccessNetwork(accessIp)
+ if accessNet != nil {
+ addr = accessIp
+ mask = netutils.Masklen2Mask(int8(accessNet.GuestIpMask)).String()
+ gateway = accessNet.GuestGateway
+ }
+ }
+ serverIP, _ := b.manager.Agent.GetDHCPServerIP()
+ args = append(args, fmt.Sprintf("dest=%s", serverIP))
+ args = append(args, fmt.Sprintf("gateway=%s", gateway))
+ args = append(args, fmt.Sprintf("addr=%s", addr))
+ args = append(args, fmt.Sprintf("mask=%s", mask))
+ }
resp += fmt.Sprintf(" append %s\n", strings.Join(args, " "))
} else {
- resp += fmt.Sprintf(" COM32 %s\n", b.getTftpFileUrl("chain.c32"))
+ resp += fmt.Sprintf(" COM32 %s\n", b.getSyslinuxPath("chain.c32", isTftp))
resp += " APPEND hd0 0\n"
b.ClearSSHConfig()
}
@@ -1038,10 +1142,18 @@ func (b *SBaremetalInstance) attachWire(mac net.HardwareAddr, wireId string, nic
}
func (b *SBaremetalInstance) postAttachWire(mac net.HardwareAddr, nicType string, netType string, ipAddr string) error {
- if nicType == api.NIC_TYPE_IPMI {
- oldIPMIConf := b.GetRawIPMIConfig()
- if oldIPMIConf != nil && oldIPMIConf.IpAddr != "" {
- ipAddr = oldIPMIConf.IpAddr
+ if ipAddr == "" {
+ switch nicType {
+ case api.NIC_TYPE_IPMI:
+ oldIPMIConf := b.GetRawIPMIConfig()
+ if oldIPMIConf != nil && oldIPMIConf.IpAddr != "" {
+ ipAddr = oldIPMIConf.IpAddr
+ }
+ case api.NIC_TYPE_ADMIN:
+ accessIp := b.GetAccessIp()
+ if accessIp != "" {
+ ipAddr = accessIp
+ }
}
}
desc, err := b.enableWire(mac, ipAddr, nicType, netType)
@@ -1060,6 +1172,7 @@ func (b *SBaremetalInstance) enableWire(mac net.HardwareAddr, ipAddr string, nic
}
if ipAddr != "" {
params.Add(jsonutils.NewString(ipAddr), "ip_addr")
+ params.Add(jsonutils.JSONTrue, "reserve")
}
if nicType == api.NIC_TYPE_IPMI {
params.Add(jsonutils.NewString("stepup"), "alloc_dir") // alloc bottom up
@@ -1114,6 +1227,11 @@ func (b *SBaremetalInstance) GetRawIPMIConfig() *types.SIPMIInfo {
return &ipmiInfo
}
+func (b *SBaremetalInstance) GetAccessIp() string {
+ accessIp, _ := b.desc.GetString("access_ip")
+ return accessIp
+}
+
func (b *SBaremetalInstance) GetServer() baremetaltypes.IBaremetalServer {
b.serverLock.Lock()
defer b.serverLock.Unlock()
@@ -1174,6 +1292,15 @@ func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI {
return ipmitool.NewLanPlusIPMI(conf.IpAddr, conf.Username, conf.Password)
}
+func (b *SBaremetalInstance) GetRedfishCli(ctx context.Context) redfish.IRedfishDriver {
+ conf := b.GetIPMIConfig()
+ if conf == nil {
+ return nil
+ }
+ return redfish.NewRedfishDriver(ctx, "https://"+conf.IpAddr,
+ conf.Username, conf.Password, false)
+}
+
func (b *SBaremetalInstance) GetIPMILanChannel() int {
conf := b.GetIPMIConfig()
if conf == nil {
@@ -1192,6 +1319,17 @@ func (b *SBaremetalInstance) DoPXEBoot() error {
return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId())
}
+func (b *SBaremetalInstance) DoRedfishPowerOn() error {
+ log.Infof("Do Redfish PowerOn ........., wait")
+ ctx := context.Background()
+ b.ClearSSHConfig()
+ redfishApi := b.GetRedfishCli(ctx)
+ if redfishApi != nil {
+ return redfishApi.Reset(ctx, "On")
+ }
+ return fmt.Errorf("Baremetal %s redfishApi is nil", b.GetId())
+}
+
/*
func (b *SBaremetalInstance) DoDiskBoot() error {
log.Infof("Do DISK Boot ........., wait")
@@ -1233,6 +1371,10 @@ func (b *SBaremetalInstance) GetZoneId() string {
return b.manager.GetZoneId()
}
+func (b *SBaremetalInstance) GetStorageCacheId() string {
+ return b.manager.Agent.CacheManager.GetId()
+}
+
func (b *SBaremetalInstance) DelayedRemove(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) {
b.remove()
return nil, nil
@@ -1279,6 +1421,11 @@ func (b *SBaremetalInstance) StartBaremetalResetBMCTask(userCred mcclient.TokenC
return nil
}
+func (b *SBaremetalInstance) StartBaremetalIpmiProbeTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error {
+ b.StartNewTask(tasks.NewBaremetalIpmiProbeTask, taskId, data)
+ return nil
+}
+
func (b *SBaremetalInstance) DelayedServerReset(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) {
err := b.DoPXEBoot()
return nil, err
@@ -1390,6 +1537,216 @@ func (b *SBaremetalInstance) DelayedServerStatus(data jsonutils.JSONObject) (jso
return resp, err
}
+func (b *SBaremetalInstance) SendNicInfo(nic *types.SNicDevInfo, idx int, nicType string, reset bool, ipAddr string, reserve bool) error {
+ params := jsonutils.NewDict()
+ params.Add(jsonutils.NewString(nic.Mac.String()), "mac")
+ params.Add(jsonutils.NewInt(int64(nic.Speed)), "rate")
+ if idx >= 0 {
+ params.Add(jsonutils.NewInt(int64(idx)), "index")
+ }
+ if nicType != "" {
+ params.Add(jsonutils.NewString(nicType), "nic_type")
+ }
+ params.Add(jsonutils.NewInt(int64(nic.Mtu)), "mtu")
+ params.Add(jsonutils.NewBool(nic.Up), "link_up")
+ if reset {
+ params.Add(jsonutils.JSONTrue, "reset")
+ }
+ if ipAddr != "" {
+ params.Add(jsonutils.NewString(ipAddr), "ip_addr")
+ params.Add(jsonutils.JSONTrue, "require_designated_ip")
+ if reserve {
+ params.Add(jsonutils.JSONTrue, "reserve")
+ }
+ }
+ resp, err := modules.Hosts.PerformAction(
+ b.GetClientSession(),
+ b.GetId(),
+ "add-netif",
+ params,
+ )
+ if err != nil {
+ return err
+ }
+ return b.SaveDesc(resp)
+}
+
+func bindMount(src, dst string) error {
+ _, err := procutils.NewCommand("touch", dst).Run()
+ if err != nil {
+ return errors.Wrapf(err, "touch %s", dst)
+ }
+ _, err = procutils.NewCommand("mount", "-o", "ro,bind", src, dst).Run()
+ if err != nil {
+ return errors.Wrapf(err, "mount %s %s", src, dst)
+ }
+ return nil
+}
+
+func unbindMount(dst string) error {
+ _, err := procutils.NewCommand("umount", dst).Run()
+ if err != nil {
+ return errors.Wrapf(err, "umount %s", dst)
+ }
+ return nil
+}
+
+func (b *SBaremetalInstance) EnablePxeBoot() bool {
+ return jsonutils.QueryBoolean(b.desc, "enable_pxe_boot", true)
+}
+
+func (b *SBaremetalInstance) GenerateBootISO() error {
+ // precheck
+ conf := b.GetRawIPMIConfig()
+ if !conf.Verified {
+ return errors.Error("GenerateBootISO: IPMI not supported")
+ }
+ if !conf.CdromBoot {
+ return errors.Error("GenerateBootISO: cdrom boot not supported")
+ }
+ accessIp := b.GetAccessIp()
+ if accessIp == "" {
+ return errors.Error("GenerateBootISO: empty accessIp")
+ }
+ adminNic := b.GetAdminNic()
+ if adminNic == nil {
+ accessNet, _ := b.findAccessNetwork(accessIp)
+ if accessNet == nil {
+ return errors.Error("GenerateBootISO: Nil access Network")
+ }
+ }
+ ctx := context.Background()
+ redfishApi := b.GetRedfishCli(ctx)
+ if redfishApi == nil {
+ return errors.Wrap(httperrors.ErrNotSupported, "no valid redfishApi")
+ }
+ // generate ISO
+ isoDir, err := ioutil.TempDir("", "bmiso")
+ if err != nil {
+ return errors.Wrap(err, "ioutil.TempDir")
+ }
+ defer os.RemoveAll(isoDir)
+ isoLinDir := filepath.Join(isoDir, "isolinux")
+ err = os.Mkdir(isoLinDir, os.FileMode(0766))
+ if err != nil {
+ return errors.Wrapf(err, "Mkdir %s", isoLinDir)
+ }
+ for _, f := range []string{
+ "chain.c32", "ldlinux.c32", "libutil.c32", "libcom32.c32",
+ } {
+ err = bindMount(filepath.Join(o.Options.TftpRoot, f), filepath.Join(isoLinDir, f))
+ if err != nil {
+ return errors.Wrapf(err, "Link %s", f)
+ }
+ defer unbindMount(filepath.Join(isoLinDir, f))
+ }
+ for src, dst := range map[string]string{
+ "kernel": "vmlinuz",
+ "initramfs": "initrd.img",
+ } {
+ err = bindMount(filepath.Join(o.Options.TftpRoot, src), filepath.Join(isoLinDir, dst))
+ if err != nil {
+ return errors.Wrapf(err, "Link %s %s", src, dst)
+ }
+ defer unbindMount(filepath.Join(isoLinDir, dst))
+ }
+ for _, f := range []string{
+ "isolinux.bin",
+ } {
+ _, err = procutils.NewCommand("cp", filepath.Join(o.Options.TftpRoot, f), filepath.Join(isoLinDir, f)).Run()
+ if err != nil {
+ return errors.Wrapf(err, "cp %s", f)
+ }
+ }
+ cfgCont := b.getIsolinuxConf()
+ err = fileutils2.FilePutContents(filepath.Join(isoLinDir, "isolinux.cfg"), cfgCont, false)
+ if err != nil {
+ return errors.Wrap(err, "fileutils.FilePutContent")
+ }
+ args := []string{
+ "-quiet",
+ "-J", "-R",
+ "-input-charset", "utf-8",
+ "-b", "isolinux/isolinux.bin",
+ "-c", "isolinux/boot.cat",
+ "-no-emul-boot",
+ "-boot-load-size", "4",
+ "-boot-info-table",
+ "-o", b.getBootIsoImagePath(),
+ isoDir,
+ }
+ _, err = procutils.NewCommand("mkisofs", args...).Run()
+ if err != nil {
+ return errors.Wrap(err, "procutils.NewCommand mkisofs")
+ }
+ // mount the virtual media
+ err = redfish.MountVirtualCdrom(ctx, redfishApi, b.getBootIsoUrl(), true)
+ if err != nil {
+ return errors.Wrap(err, "redfish.MountVirtualCdrom")
+ }
+ return nil
+}
+
+func (b *SBaremetalInstance) clearBootIso() error {
+ ctx := context.Background()
+ redfishApi := b.GetRedfishCli(ctx)
+ if redfishApi == nil {
+ return errors.Wrap(httperrors.ErrNotSupported, "no valid redfishApi")
+ }
+ err := redfish.UmountVirtualCdrom(ctx, redfishApi)
+ if err != nil {
+ return errors.Wrap(err, "redfish.UmountVirtualCdrom")
+ }
+ return b.clearBootIsoImage()
+}
+
+func (b *SBaremetalInstance) clearBootIsoImage() error {
+ path := b.getBootIsoImagePath()
+ if fileutils2.Exists(path) {
+ return os.Remove(path)
+ }
+ return nil
+}
+
+func (b *SBaremetalInstance) getBootIsoImagePath() string {
+ return filepath.Join(o.Options.BootIsoPath, b.GetId()+".iso")
+}
+
+func (b *SBaremetalInstance) DoNTPConfig() error {
+ var urls []string
+ for _, ep := range []string{"internal", "public"} {
+ urls, _ = auth.GetServiceURLs("ntp", o.Options.Region, "", ep)
+ if len(urls) > 0 {
+ break
+ }
+ }
+ if len(urls) == 0 {
+ log.Warningf("NO ntp server specified, skip DoNTPConfig")
+ return nil
+ }
+ for i := range urls {
+ if strings.HasPrefix(urls[i], "ntp://") {
+ urls[i] = urls[i][6:]
+ }
+ }
+ log.Infof("Set NTP %s", urls)
+ ntpConf := redfish.SNTPConf{}
+ ntpConf.ProtocolEnabled = true
+ ntpConf.TimeZone = o.Options.TimeZone
+ ntpConf.NTPServers = urls
+
+ ctx := context.Background()
+ redfishApi := b.GetRedfishCli(ctx)
+ if redfishApi == nil {
+ return errors.Wrap(httperrors.ErrNotSupported, "no valid redfishApi")
+ }
+ err := redfishApi.SetNTPConf(ctx, ntpConf)
+ if err != nil {
+ return errors.Wrap(err, "redfishApi.SetNTPConf")
+ }
+ return nil
+}
+
type SBaremetalServer struct {
baremetal *SBaremetalInstance
desc *jsonutils.JSONDict
@@ -1548,14 +1905,7 @@ func replaceHostAddr(urlStr string, addr string) string {
func (s *SBaremetalServer) doCreateRoot(term *ssh.Client, devName string) error {
session := s.baremetal.GetClientSession()
token := session.GetToken().GetTokenString()
- urlStr, err := session.GetServiceURL("image", "internalURL")
- if err != nil {
- return err
- }
- // this is hackish, url should point to an image proxy
- // XXX
- listenIp, _ := s.baremetal.manager.Agent.GetListenIP()
- urlStr = replaceHostAddr(urlStr, listenIp.String())
+ urlStr := s.baremetal.getImageCacheUrl()
imageId := s.GetRootTemplateId()
cmd := fmt.Sprintf("/lib/mos/rootcreate.sh %s %s %s %s", token, urlStr, imageId, devName)
log.Infof("rootcreate cmd: %q", cmd)
@@ -1592,7 +1942,7 @@ func (s *SBaremetalServer) DoPartitionDisk(term *ssh.Client) ([]*disktool.Partit
disks, _ := s.desc.GetArray("disks")
if len(disks) == 0 {
- return nil, errors.New("Empty disks in desc")
+ return nil, errors.Error("Empty disks in desc")
}
rootDisk := disks[0]
@@ -1605,7 +1955,7 @@ func (s *SBaremetalServer) DoPartitionDisk(term *ssh.Client) ([]*disktool.Partit
tool.RetrievePartitionInfo()
parts := tool.GetPartitions()
if len(parts) == 0 {
- return nil, errors.New("Root disk create failed, no partitions")
+ return nil, errors.Error("Root disk create failed, no partitions")
}
log.Infof("Resize root to %d MB", rootSize)
if err := tool.ResizePartition(0, rootSize); err != nil {
diff --git a/pkg/baremetal/nic.go b/pkg/baremetal/nic.go
index 22be7861aa..6a23b216be 100644
--- a/pkg/baremetal/nic.go
+++ b/pkg/baremetal/nic.go
@@ -76,11 +76,12 @@ func GetNicDHCPConfig(
case 6:
conf.BootFile = "bootia32.efi"
default:
- bootFile := "pxelinux.0"
- if o.Options.EnableTftpHttpDownload {
- bootFile = "lpxelinux.0"
- }
- conf.BootFile = bootFile
+ //if o.Options.EnableTftpHttpDownload {
+ // bootFile = "lpxelinux.0"
+ //}else {
+ // bootFile := "pxelinux.0"
+ //}
+ conf.BootFile = "lpxelinux.0"
}
pxePath := filepath.Join(o.Options.TftpRoot, conf.BootFile)
if f, err := os.Open(pxePath); err != nil {
diff --git a/pkg/baremetal/options/options.go b/pkg/baremetal/options/options.go
index 9c34597464..bd68ef6af0 100644
--- a/pkg/baremetal/options/options.go
+++ b/pkg/baremetal/options/options.go
@@ -41,7 +41,11 @@ type BaremetalOptions struct {
DefaultStrongIpmiPassword string `help:"Default strong IPMI passowrd"`
WindowsDefaultAdminUser bool `default:"true" help:"Default account for Windows system is Administrator"`
- EnableTftpHttpDownload bool `default:"true" help:"Pxelinux download file through http"`
+ // EnableTftpHttpDownload bool `default:"true" help:"Pxelinux download file through http"`
+
+ CachePath string `help:"local image cache directory"`
+ EnablePxeBoot bool `help:"Enable DHCP PXE boot" default:"true"`
+ BootIsoPath string `help:"iso boot image path"`
}
var (
diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go
index f06bdc5b8f..08f1491243 100644
--- a/pkg/baremetal/pxe/dhcp.go
+++ b/pkg/baremetal/pxe/dhcp.go
@@ -15,13 +15,13 @@
package pxe
import (
- "errors"
"fmt"
"net"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
o "yunion.io/x/onecloud/pkg/baremetal/options"
@@ -130,10 +130,10 @@ func (h *DHCPHandler) newRequest(pkt dhcp.Packet, man IBaremetalManager) (*dhcpR
// expect to boot.
case 17:
if data[0] != 0 {
- err = errors.New("malformed client GUID (option 97), leading byte must be zero")
+ err = errors.Error("malformed client GUID (option 97), leading byte must be zero")
}
default:
- err = errors.New("malformed client GUID (option 97), wrong size")
+ err = errors.Error("malformed client GUID (option 97), wrong size")
}
cliGuid, err = req.Options.String(optCode)
}
@@ -158,6 +158,9 @@ func (req *dhcpRequest) fetchConfig(session *mcclient.ClientSession) (*dhcp.Resp
// TODO: set cache for netConf
if req.isPXERequest() {
+ if !o.Options.EnablePxeBoot {
+ return nil, errors.Error("PXE Boot disabled")
+ }
// handle PXE DHCP request
log.Infof("DHCP relay from %s(%s) for %s, find matched networks: %#v", req.RelayAddr, req.ClientAddr, req.ClientMac, netConf)
bmDesc, err := req.createOrUpdateBaremetal(session)
@@ -389,10 +392,10 @@ func (s *Server) validateDHCP(pkt dhcp.Packet) (Machine, Firmware, error) {
// well accept these buggy ROMs.
case 17:
if guid[0] != 0 {
- return mach, 0, errors.New("malformed client GUID (option 97), leading byte must be zero")
+ return mach, 0, errors.Error("malformed client GUID (option 97), leading byte must be zero")
}
default:
- return mach, 0, errors.New("malformed client GUID (option 97), wrong size")
+ return mach, 0, errors.Error("malformed client GUID (option 97), wrong size")
}
mach.MAC = pkt.CHAddr()
diff --git a/pkg/baremetal/service/service.go b/pkg/baremetal/service/service.go
index 086faa80f9..b039210ef3 100644
--- a/pkg/baremetal/service/service.go
+++ b/pkg/baremetal/service/service.go
@@ -15,12 +15,12 @@
package service
import (
- "fmt"
- "net/http"
"os"
+ "path/filepath"
"yunion.io/x/log"
+ "yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/baremetal"
"yunion.io/x/onecloud/pkg/baremetal/handler"
o "yunion.io/x/onecloud/pkg/baremetal/options"
@@ -29,6 +29,8 @@ import (
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/cloudcommon/service"
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
+
+ _ "yunion.io/x/onecloud/pkg/util/redfish/loader"
)
type BaremetalService struct {
@@ -41,13 +43,29 @@ func New() *BaremetalService {
func (s *BaremetalService) StartService() {
common_options.ParseOptions(&o.Options, os.Args, "baremetal.conf", "baremetal")
- app_common.InitAuth(&o.Options.CommonOptions, s.startAgent)
+
+ if len(o.Options.CachePath) == 0 {
+ o.Options.CachePath = filepath.Join(filepath.Dir(o.Options.BaremetalsPath), "bm_image_cache")
+ log.Infof("No cachepath, use default %s", o.Options.CachePath)
+ }
+ if len(o.Options.BootIsoPath) == 0 {
+ o.Options.BootIsoPath = filepath.Join(filepath.Dir(o.Options.BaremetalsPath), "bm_boot_iso")
+ log.Infof("No BootIsoPath, use default %s", o.Options.BootIsoPath)
+ err := os.MkdirAll(o.Options.BootIsoPath, os.FileMode(0760))
+ if err != nil {
+ log.Fatalf("fail to create BootIsoPath %s", o.Options.BootIsoPath)
+ }
+ }
+
+ app_common.InitAuth(&o.Options.CommonOptions, func() {
+ log.Infof("auth complete")
+ })
fsdriver.Init(nil)
app := app_common.InitApp(&o.Options.BaseOptions, false)
handler.InitHandlers(app)
- s.startFileServer()
+ s.startAgent(app)
app_common.ServeForeverWithCleanup(app, &o.Options.BaseOptions, func() {
tasks.OnStop()
@@ -55,21 +73,8 @@ func (s *BaremetalService) StartService() {
})
}
-func (s *BaremetalService) startFileServer() {
- if !o.Options.EnableTftpHttpDownload {
- return
- }
- fs := http.FileServer(http.Dir(o.Options.TftpRoot))
- http.Handle("/tftp/", http.StripPrefix("/tftp/", fs))
- go func() {
- if err := http.ListenAndServe(fmt.Sprintf("%s:%d", o.Options.Address, o.Options.Port+1000), nil); err != nil {
- panic(fmt.Sprintf("start http file server: %v", err))
- }
- }()
-}
-
-func (s *BaremetalService) startAgent() {
- err := baremetal.Start()
+func (s *BaremetalService) startAgent(app *appsrv.Application) {
+ err := baremetal.Start(app)
if err != nil {
log.Fatalf("Start agent error: %v", err)
}
diff --git a/pkg/baremetal/tasks/base.go b/pkg/baremetal/tasks/base.go
index 33dddc7753..f22e8f85c9 100644
--- a/pkg/baremetal/tasks/base.go
+++ b/pkg/baremetal/tasks/base.go
@@ -21,11 +21,11 @@ import (
"sync"
"time"
- "github.com/pkg/errors"
-
"yunion.io/x/jsonutils"
"yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
+ o "yunion.io/x/onecloud/pkg/baremetal/options"
"yunion.io/x/onecloud/pkg/cloudcommon/types"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/ssh"
@@ -181,6 +181,7 @@ type TaskFactory func(bm IBaremetal, taskId string, data jsonutils.JSONObject) (
type SBaremetalTaskBase struct {
Baremetal IBaremetal
+ PxeBoot bool
userCred mcclient.TokenCredential
stageFunc TaskStageFunc
sshStageFunc SSHTaskStageFunc
@@ -286,14 +287,18 @@ func (self *SBaremetalTaskBase) EnsurePowerShutdown(soft bool) error {
}
func (self *SBaremetalTaskBase) EnsurePowerUp() error {
- log.Infof("EnsurePowerUp: bootdev=pxe")
+ log.Infof("EnsurePowerUp: bootdev=pxe %v", self.PxeBoot)
status, err := self.Baremetal.GetPowerStatus()
if err != nil {
return errors.Wrapf(err, "Get power status")
}
for status == "" || status == types.POWER_STATUS_OFF {
if status == types.POWER_STATUS_OFF {
- err = self.Baremetal.DoPXEBoot()
+ if self.PxeBoot {
+ err = self.Baremetal.DoPXEBoot()
+ } else {
+ err = self.Baremetal.DoRedfishPowerOn()
+ }
if err != nil {
return errors.Wrapf(err, "Do PXE boot")
}
@@ -353,6 +358,18 @@ func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(pxeBootTask IPXEBootTask,
pxeBootTask.SetSSHStageParams(pxeBootTask, sshConf.RemoteIP, sshConf.Password)
return self, nil
}
+
+ // generate ISO
+ if err := self.Baremetal.GenerateBootISO(); err != nil {
+ log.Errorf("GenerateBootISO fail: %s", err)
+ if !o.Options.EnablePxeBoot || !self.Baremetal.EnablePxeBoot() {
+ return self, errors.Wrap(err, "self.Baremetal.GenerateBootISO")
+ }
+ self.PxeBoot = true
+ } else {
+ self.PxeBoot = false
+ }
+
// Do soft reboot
if data != nil && jsonutils.QueryBoolean(data, "soft_boot", false) {
self.startTime = time.Now()
@@ -364,12 +381,14 @@ func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(pxeBootTask IPXEBootTask,
return self, nil
}
+
// shutdown and power up to PXE mode
if err := self.EnsurePowerShutdown(false); err != nil {
- return self, fmt.Errorf("EnsurePowerShutdown: %v", err)
+ return self, errors.Wrap(err, "EnsurePowerShutdown")
}
+
if err := self.EnsurePowerUp(); err != nil {
- return self, errors.Wrapf(err, "EnsurePowerUp to pxe")
+ return self, errors.Wrap(err, "EnsurePowerUp to pxe")
}
// this stage will be called by baremetalInstance when pxe start notify
self.SetSSHStage(pxeBootTask.OnPXEBoot)
diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go
index 3378a665fb..4979539ba8 100644
--- a/pkg/baremetal/tasks/baseprepare.go
+++ b/pkg/baremetal/tasks/baseprepare.go
@@ -21,6 +21,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/pkg/util/seclib"
"yunion.io/x/pkg/utils"
@@ -110,8 +111,13 @@ func (task *sBaremetalPrepareTask) prepareBaremetalInfo(cli *ssh.Client) (*barem
return nil, err
}
- ipmiInfo := &types.SIPMIInfo{
- Present: ipmiEnable,
+ ipmiInfo := task.baremetal.GetRawIPMIConfig()
+ if ipmiInfo == nil {
+ ipmiInfo = &types.SIPMIInfo{}
+ }
+ if !ipmiInfo.Present && ipmiEnable {
+ ipmiInfo.Present = true
+ ipmiInfo.Verified = false
}
return &baremetalPrepareInfo{
@@ -131,6 +137,11 @@ func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremet
return nil
}
+ // if verified, skip ipmi config
+ if i.ipmiInfo.Verified {
+ return nil
+ }
+
var (
sysInfo = i.sysInfo
ipmiInfo = i.ipmiInfo
@@ -138,8 +149,8 @@ func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremet
sshIPMI := ipmitool.NewSSHIPMI(cli)
// ipmitool.SetSysInfo
ipmiSysInfo := sysInfo.ToIPMISystemInfo()
- SetIPMILanPortShared(sshIPMI, ipmiSysInfo)
- ipmiUser, ipmiPasswd, ipmiIpAddr := task.getIPMIUserPasswd(ipmiSysInfo)
+ setIPMILanPortShared(sshIPMI, ipmiSysInfo)
+ ipmiUser, ipmiPasswd, ipmiIpAddr := task.getIPMIUserPasswd(i.ipmiInfo, ipmiSysInfo)
ipmiInfo.Username = ipmiUser
ipmiInfo.Password = ipmiPasswd
@@ -162,7 +173,8 @@ func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremet
Speed: 100,
Mtu: 1500,
}
- if err := task.sendNicInfo(ipmiNic, -1, api.NIC_TYPE_IPMI, true, ""); err != nil {
+ if err := task.sendNicInfo(ipmiNic, -1, api.NIC_TYPE_IPMI, true, "", false); err != nil {
+ // ignore the error
log.Errorf("Send IPMI nic %#v info: %v", ipmiNic, err)
}
rootId := ipmitool.GetRootId(ipmiSysInfo)
@@ -206,6 +218,7 @@ func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremet
err = ipmitool.SetLanDHCP(sshIPMI, lanChannel)
if err != nil {
+ // ignore error
log.Errorf("Set lan channel %d dhcp error: %v", lanChannel, err)
}
time.Sleep(2 * time.Second)
@@ -266,6 +279,7 @@ func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremet
return fmt.Errorf("Fail to get IPMI address from DHCP")
}
ipmiInfo.LanChannel = ipmiLanChannel
+ ipmiInfo.Verified = true
return nil
}
@@ -284,12 +298,51 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error {
return err
}
+ // set NTP
+ if err = task.baremetal.DoNTPConfig(); err != nil {
+ // ignore error
+ log.Errorf("SetNTP fail: %s", err)
+ }
+
log.Infof("Prepare complete")
return nil
}
+func (task *sBaremetalPrepareTask) findAdminNic(cli *ssh.Client, nicsInfo []*types.SNicDevInfo) (int, *types.SNicDevInfo, error) {
+ for idx := range nicsInfo {
+ nic := nicsInfo[idx]
+ output, err := cli.Run("/sbin/ifconfig " + nic.Dev)
+ if err != nil {
+ log.Errorf("ifconfig %s fail: %s", nic.Dev, err)
+ continue
+ }
+ isAdmin := false
+ for _, l := range output {
+ if strings.Contains(l, task.baremetal.GetAccessIp()) {
+ isAdmin = true
+ break
+ }
+ }
+ if isAdmin {
+ return idx, nic, nil
+ }
+ }
+ return -1, nil, errors.Error("admin nic not found???")
+}
+
func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPrepareInfo) error {
adminNic := task.baremetal.GetAdminNic()
+ if adminNic == nil {
+ adminIdx, adminNicDev, err := task.findAdminNic(cli, i.nicsInfo)
+ if err != nil {
+ return errors.Wrap(err, "task.findAdminNic")
+ }
+ err = task.sendNicInfo(adminNicDev, adminIdx, api.NIC_TYPE_ADMIN, false, task.baremetal.GetAccessIp(), true)
+ if err != nil {
+ return errors.Wrap(err, "send Admin Nic Info")
+ }
+ adminNic = task.baremetal.GetAdminNic()
+ }
// collect params
updateInfo := make(map[string]interface{})
oname := fmt.Sprintf("BM%s", strings.Replace(adminNic.Mac, ":", "", -1))
@@ -297,6 +350,7 @@ func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPre
updateInfo["name"] = fmt.Sprintf("BM-%s", strings.Replace(i.ipmiInfo.IpAddr, ".", "-", -1))
}
updateInfo["access_ip"] = adminNic.IpAddr
+ updateInfo["access_mac"] = adminNic.Mac
updateInfo["cpu_count"] = i.cpuInfo.Count
updateInfo["node_count"] = i.dmiCpuInfo.Nodes
updateInfo["cpu_desc"] = i.cpuInfo.Model
@@ -315,9 +369,11 @@ func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPre
_, err := modules.Hosts.Update(task.getClientSession(), task.baremetal.GetId(), updateData)
if err != nil {
log.Errorf("Update baremetal info error: %v", err)
+ return errors.Wrap(err, "Hosts.Update")
}
if err := task.sendStorageInfo(size); err != nil {
log.Errorf("sendStorageInfo error: %v", err)
+ return errors.Wrap(err, "task.sendStorageInfo")
}
// XXX do not change nic order anymore
// for i := range nicsInfo {
@@ -337,19 +393,24 @@ func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPre
err = task.removeNicInfo(removedMacs[idx])
if err != nil {
log.Errorf("Fail to remove Netif %s: %s", removedMacs[idx], err)
+ return errors.Wrap(err, "task.removeNicInfo")
}
}
for idx := range i.nicsInfo {
- err = task.sendNicInfo(i.nicsInfo[idx], idx, "", false, "")
+ err = task.sendNicInfo(i.nicsInfo[idx], idx, "", false, "", false)
if err != nil {
log.Errorf("Send nicinfo idx: %d, %#v error: %v", idx, i.nicsInfo[idx], err)
+ return errors.Wrap(err, "task.sendNicInfo")
}
}
- for _, nicInfo := range i.nicsInfo {
- if nicInfo.Mac.String() != adminNic.GetMac().String() && nicInfo.Up {
- err = task.doNicWireProbe(cli, nicInfo)
- if err != nil {
- log.Errorf("doNicWireProbe nic %#v error: %v", nicInfo, err)
+ if o.Options.EnablePxeBoot && task.baremetal.EnablePxeBoot() {
+ for _, nicInfo := range i.nicsInfo {
+ if nicInfo.Mac.String() != adminNic.GetMac().String() && nicInfo.Up {
+ err = task.doNicWireProbe(cli, nicInfo)
+ if err != nil {
+ // ignore the error
+ log.Errorf("doNicWireProbe nic %#v error: %v", nicInfo, err)
+ }
}
}
}
@@ -431,7 +492,7 @@ func (task *sBaremetalPrepareTask) tryLocalIpmiAddr(sshIPMI *ipmitool.SSHIPMI, i
if tried < maxTries {
// make sure the ipaddr is a IPMI address
// enable the netif
- err := task.sendNicInfo(ipmiNic, -1, api.NIC_TYPE_IPMI, false, tryAddr)
+ err := task.sendNicInfo(ipmiNic, -1, api.NIC_TYPE_IPMI, false, tryAddr, true)
if err != nil {
log.Errorf("Fail to set existing BMC IP address to %s", tryAddr)
} else {
@@ -441,7 +502,7 @@ func (task *sBaremetalPrepareTask) tryLocalIpmiAddr(sshIPMI *ipmitool.SSHIPMI, i
return false
}
-func (task *sBaremetalPrepareTask) getIPMIUserPasswd(sysInfo *types.SIPMISystemInfo) (string, string, string) {
+func (task *sBaremetalPrepareTask) getIPMIUserPasswd(oldIPMIConf *types.SIPMIInfo, sysInfo *types.SIPMISystemInfo) (string, string, string) {
var (
ipmiUser string
ipmiPasswd string
@@ -458,17 +519,14 @@ func (task *sBaremetalPrepareTask) getIPMIUserPasswd(sysInfo *types.SIPMISystemI
} else {
ipmiPasswd = seclib.RandomPassword(20)
}
- oldIPMIConf := task.baremetal.GetRawIPMIConfig()
- if oldIPMIConf != nil {
- if oldIPMIConf.Username != "" {
- ipmiUser = oldIPMIConf.Username
- }
- if oldIPMIConf.Password != "" {
- ipmiPasswd = oldIPMIConf.Password
- }
- if oldIPMIConf.IpAddr != "" {
- ipmiIpAddr = oldIPMIConf.IpAddr
- }
+ if oldIPMIConf.Username != "" {
+ ipmiUser = oldIPMIConf.Username
+ }
+ if oldIPMIConf.Password != "" {
+ ipmiPasswd = oldIPMIConf.Password
+ }
+ if oldIPMIConf.IpAddr != "" {
+ ipmiIpAddr = oldIPMIConf.IpAddr
}
return ipmiUser, ipmiPasswd, ipmiIpAddr
}
@@ -502,19 +560,6 @@ func (task *sBaremetalPrepareTask) getClientSession() *mcclient.ClientSession {
return task.baremetal.GetClientSession()
}
-func (task *sBaremetalPrepareTask) removeAllNics() error {
- resp, err := modules.Hosts.PerformAction(
- task.getClientSession(),
- task.baremetal.GetId(),
- "remove-all-netifs",
- nil,
- )
- if err != nil {
- return nil
- }
- return task.baremetal.SaveDesc(resp)
-}
-
func getDMISysinfo(cli *ssh.Client) (*types.SDMISystemInfo, error) {
ret, err := cli.Run("/usr/sbin/dmidecode -t 1")
if err != nil {
@@ -578,41 +623,15 @@ func (task *sBaremetalPrepareTask) removeNicInfo(mac string) error {
return task.baremetal.SaveDesc(resp)
}
-func (task *sBaremetalPrepareTask) sendNicInfo(nic *types.SNicDevInfo, idx int, nicType string, reset bool, ipAddr string) error {
- params := jsonutils.NewDict()
- params.Add(jsonutils.NewString(nic.Mac.String()), "mac")
- params.Add(jsonutils.NewInt(int64(nic.Speed)), "rate")
- if idx >= 0 {
- params.Add(jsonutils.NewInt(int64(idx)), "index")
- }
- if nicType != "" {
- params.Add(jsonutils.NewString(nicType), "nic_type")
- }
- params.Add(jsonutils.NewInt(int64(nic.Mtu)), "mtu")
- params.Add(jsonutils.NewBool(nic.Up), "link_up")
- if reset {
- params.Add(jsonutils.JSONTrue, "reset")
- }
- if ipAddr != "" {
- params.Add(jsonutils.NewString(ipAddr), "ip_addr")
- params.Add(jsonutils.JSONTrue, "require_designated_ip")
- }
- resp, err := modules.Hosts.PerformAction(
- task.getClientSession(),
- task.baremetal.GetId(),
- "add-netif",
- params,
- )
- if err != nil {
- return err
- }
- return task.baremetal.SaveDesc(resp)
+func (task *sBaremetalPrepareTask) sendNicInfo(nic *types.SNicDevInfo, idx int, nicType string, reset bool, ipAddr string, reserve bool) error {
+ return task.baremetal.SendNicInfo(nic, idx, nicType, reset, ipAddr, reserve)
}
func (task *sBaremetalPrepareTask) sendStorageInfo(size int64) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewInt(size), "capacity")
params.Add(jsonutils.NewString(task.baremetal.GetZoneId()), "zone_id")
+ params.Add(jsonutils.NewString(task.baremetal.GetStorageCacheId()), "storagecache_id")
_, err := modules.Hosts.PerformAction(task.getClientSession(), task.baremetal.GetId(), "update-storage", params)
return err
}
@@ -656,7 +675,7 @@ func (task *sBaremetalPrepareTask) collectDiskInfo(diskInfo []*baremetal.Baremet
return size, diskType
}
-func SetIPMILanPortShared(cli ipmitool.IPMIExecutor, sysInfo *types.SIPMISystemInfo) {
+func setIPMILanPortShared(cli ipmitool.IPMIExecutor, sysInfo *types.SIPMISystemInfo) {
if !o.Options.IpmiLanPortShared {
return
}
diff --git a/pkg/baremetal/tasks/bm_register.go b/pkg/baremetal/tasks/bm_register.go
index 40081fa8b4..033b5e34ae 100644
--- a/pkg/baremetal/tasks/bm_register.go
+++ b/pkg/baremetal/tasks/bm_register.go
@@ -171,7 +171,7 @@ func (s *sBaremetalRegisterTask) updateIpmiInfo(cli *ssh.Client) {
} else {
nic.Mac = conf.Mac
}
- s.sendNicInfo(nic, -1, api.NIC_TYPE_IPMI, false, "")
+ s.sendNicInfo(nic, -1, api.NIC_TYPE_IPMI, false, "", false)
}
func (s *sBaremetalRegisterTask) updateBmInfo(cli *ssh.Client, i *baremetalPrepareInfo) error {
@@ -200,7 +200,7 @@ func (s *sBaremetalRegisterTask) updateBmInfo(cli *ssh.Client, i *baremetalPrepa
log.Errorf("sendStorageInfo error: %v", err)
}
for idx := range i.nicsInfo {
- err = s.sendNicInfo(i.nicsInfo[idx], idx, "", false, "")
+ err = s.sendNicInfo(i.nicsInfo[idx], idx, "", false, "", false)
if err != nil {
log.Errorf("Send nicinfo idx: %d, %#v error: %v", idx, i.nicsInfo[idx], err)
}
diff --git a/pkg/baremetal/tasks/interface.go b/pkg/baremetal/tasks/interface.go
index c3fdbaee49..4883a3c13b 100644
--- a/pkg/baremetal/tasks/interface.go
+++ b/pkg/baremetal/tasks/interface.go
@@ -28,6 +28,7 @@ import (
type IBaremetal interface {
GetId() string
GetZoneId() string
+ GetStorageCacheId() string
GetTaskQueue() *TaskQueue
GetSSHConfig() (*types.SSHConfig, error)
TestSSHConfig() bool
@@ -52,6 +53,13 @@ type IBaremetal interface {
DoPXEBoot() error
// DoDiskBoot() error
+ DoRedfishPowerOn() error
+ GetAccessIp() string
+ EnablePxeBoot() bool
+ GenerateBootISO() error
+ SendNicInfo(nic *types.SNicDevInfo, idx int, nicType string, reset bool, ipAddr string, reserve bool) error
+ DoNTPConfig() error
+
RemoveServer()
InitializeServer(name string) error
SaveSSHConfig(remoteAddr string, key string) error
diff --git a/pkg/baremetal/tasks/ipmiprobe.go b/pkg/baremetal/tasks/ipmiprobe.go
new file mode 100644
index 0000000000..01aaf85264
--- /dev/null
+++ b/pkg/baremetal/tasks/ipmiprobe.go
@@ -0,0 +1,227 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tasks
+
+import (
+ "context"
+ "net"
+
+ "yunion.io/x/jsonutils"
+ "yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
+
+ api "yunion.io/x/onecloud/pkg/apis/compute"
+ o "yunion.io/x/onecloud/pkg/baremetal/options"
+ "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool"
+ "yunion.io/x/onecloud/pkg/cloudcommon/types"
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/mcclient/modules"
+ "yunion.io/x/onecloud/pkg/util/redfish"
+)
+
+type SBaremetalIpmiProbeTask struct {
+ *SBaremetalTaskBase
+}
+
+func NewBaremetalIpmiProbeTask(
+ baremetal IBaremetal,
+ taskId string,
+ data jsonutils.JSONObject,
+) (ITask, error) {
+ baseTask := newBaremetalTaskBase(baremetal, taskId, data)
+ task := new(SBaremetalIpmiProbeTask)
+ task.SBaremetalTaskBase = baseTask
+ task.SetStage(task.DoIpmiProbe)
+ log.Debugf("Start SBaremetalIpmiProbeTask XXXXXX!!!!!")
+ return task, nil
+}
+
+func (self *SBaremetalIpmiProbeTask) GetName() string {
+ return "BaremetalIpmiProbeTask"
+}
+
+func (self *SBaremetalIpmiProbeTask) DoIpmiProbe(ctx context.Context, args interface{}) error {
+ ipmiInfo := self.Baremetal.GetRawIPMIConfig()
+ if ipmiInfo == nil {
+ ipmiInfo = &types.SIPMIInfo{}
+ }
+ if ipmiInfo.IpAddr == "" {
+ return errors.Error("empty IPMI ip_addr")
+ }
+ if ipmiInfo.Username == "" {
+ return errors.Error("empty IPMI username")
+ }
+ if ipmiInfo.Password == "" {
+ return errors.Error("empty IPMI password")
+ }
+ redfishCli := redfish.NewRedfishDriver(ctx, "https://"+ipmiInfo.IpAddr, ipmiInfo.Username, ipmiInfo.Password, false)
+ if redfishCli != nil {
+ return self.doRedfishIpmiProbe(ctx, redfishCli)
+ } else {
+ log.Warningf("BMC not redfish-compatible")
+ ipmiTool := ipmitool.NewLanPlusIPMI(ipmiInfo.IpAddr, ipmiInfo.Username, ipmiInfo.Password)
+ return self.doRawIpmiProbe(ctx, ipmiTool)
+ }
+}
+
+func (self *SBaremetalIpmiProbeTask) doRedfishIpmiProbe(ctx context.Context, drv redfish.IRedfishDriver) error {
+ confs, err := drv.GetLanConfigs(ctx)
+ if err != nil {
+ return errors.Wrap(err, "drv.GetLanConfigs")
+ }
+ if len(confs) == 0 {
+ return errors.Wrap(httperrors.ErrNotFound, "no IPMI lan")
+ }
+ err = self.sendIpmiNicInfo(&confs[0])
+ if err != nil {
+ return errors.Wrap(err, "self.sendIpmiNicInfo")
+ }
+ _, sysInfo, err := drv.GetSystemInfo(ctx)
+ if err != nil {
+ return errors.Wrap(err, "drv.GetSystemInfo")
+ }
+ updateInfo := make(map[string]interface{})
+ if len(sysInfo.EthernetNICs) > 0 {
+ updateInfo["access_mac"] = sysInfo.EthernetNICs[0]
+ }
+ updateInfo["node_count"] = sysInfo.NodeCount
+ updateInfo["cpu_count"] = sysInfo.NodeCount
+ updateInfo["cpu_desc"] = sysInfo.CpuDesc
+ updateInfo["mem_size"] = sysInfo.MemoryGB * 1024
+ updateInfo["sn"] = sysInfo.SerialNumber
+ dmiSysInfo := &types.SDMISystemInfo{
+ Manufacture: sysInfo.Manufacturer,
+ Model: sysInfo.Model,
+ SN: sysInfo.SerialNumber,
+ }
+ updateInfo["sys_info"] = dmiSysInfo
+ updateInfo["is_baremetal"] = true
+ ipmiInfo := self.Baremetal.GetRawIPMIConfig()
+ if ipmiInfo == nil {
+ ipmiInfo = &types.SIPMIInfo{}
+ }
+ ipmiInfo.Present = true
+ ipmiInfo.Verified = true
+ ipmiInfo.RedfishApi = true
+ _, cdInfo, _ := drv.GetVirtualCdromInfo(ctx)
+ ipmiInfo.CdromBoot = cdInfo.SupportAction
+ ipmiInfo.PxeBoot = o.Options.EnablePxeBoot
+ updateData := jsonutils.Marshal(updateInfo)
+ updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams())
+ _, err = modules.Hosts.Update(self.Baremetal.GetClientSession(), self.Baremetal.GetId(), updateData)
+ if err != nil {
+ log.Errorf("Update baremetal info error: %v", err)
+ return errors.Wrap(err, "modules.Hosts.Update")
+ }
+ for i := range sysInfo.EthernetNICs {
+ mac, err := net.ParseMAC(sysInfo.EthernetNICs[i])
+ if err == nil {
+ err = self.sendNicInfo(i, mac)
+ if err != nil {
+ return errors.Wrapf(err, "sendNicInfo %d %s", i, mac)
+ }
+ }
+ }
+ self.Baremetal.SyncStatus("", "Probe Redfish finished")
+ SetTaskComplete(self, nil)
+ return nil
+}
+
+func (self *SBaremetalIpmiProbeTask) sendIpmiNicInfo(lanConf *types.SIPMILanConfig) error {
+ speed := lanConf.SpeedMbps
+ if speed <= 0 {
+ speed = 100
+ }
+ ipmiNic := &types.SNicDevInfo{
+ Mac: lanConf.Mac,
+ Up: true,
+ Speed: speed,
+ Mtu: 1500,
+ }
+ err := self.Baremetal.SendNicInfo(ipmiNic, -1, api.NIC_TYPE_IPMI, true, lanConf.IPAddr, true)
+ if err != nil {
+ return errors.Wrap(err, "SendNicInfo")
+ }
+ return nil
+}
+
+func (self *SBaremetalIpmiProbeTask) sendNicInfo(index int, mac net.HardwareAddr) error {
+ nicInfo := &types.SNicDevInfo{
+ Mac: mac,
+ }
+ err := self.Baremetal.SendNicInfo(nicInfo, index, "", true, "", false)
+ if err != nil {
+ return errors.Wrap(err, "SendNicInfo")
+ }
+ return nil
+}
+
+func (self *SBaremetalIpmiProbeTask) doRawIpmiProbe(ctx context.Context, cli ipmitool.IPMIExecutor) error {
+ sysInfo, err := ipmitool.GetSysInfo(cli)
+ if err != nil {
+ // ignore error for qemu
+ log.Errorf("ipmitool.GetSysInfo error %s", err)
+ }
+ var conf *types.SIPMILanConfig
+ var channel int
+ for _, lanChannel := range ipmitool.GetLanChannels(sysInfo) {
+ conf, err = ipmitool.GetLanConfig(cli, lanChannel)
+ if err != nil {
+ // ignore error
+ log.Errorf("ipmitool.GetLanConfig for channel %d fail: %s", lanChannel, err)
+ } else {
+ channel = lanChannel
+ break
+ }
+ }
+ if conf == nil {
+ return errors.Wrap(httperrors.ErrNotFound, "no IPMI lan")
+ }
+ err = self.sendIpmiNicInfo(conf)
+ if err != nil {
+ return errors.Wrap(err, "self.sendIpmiNicInfo")
+ }
+ updateInfo := make(map[string]interface{})
+ if len(sysInfo.SN) > 0 {
+ updateInfo["sn"] = sysInfo.SN
+ dmiSysInfo := &types.SDMISystemInfo{
+ Manufacture: sysInfo.Manufacture,
+ Model: sysInfo.Model,
+ Version: sysInfo.Version,
+ SN: sysInfo.SN,
+ }
+ updateInfo["sys_info"] = dmiSysInfo
+ }
+ updateInfo["is_baremetal"] = true
+ ipmiInfo := self.Baremetal.GetRawIPMIConfig()
+ if ipmiInfo == nil {
+ ipmiInfo = &types.SIPMIInfo{}
+ }
+ ipmiInfo.Present = true
+ ipmiInfo.Verified = true
+ ipmiInfo.RedfishApi = false
+ ipmiInfo.CdromBoot = false
+ ipmiInfo.PxeBoot = o.Options.EnablePxeBoot
+ ipmiInfo.LanChannel = channel
+ updateData := jsonutils.Marshal(updateInfo)
+ updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams())
+ _, err = modules.Hosts.Update(self.Baremetal.GetClientSession(), self.Baremetal.GetId(), updateData)
+ if err != nil {
+ return errors.Wrap(err, "modules.Hosts.Update")
+ }
+ self.Baremetal.SyncStatus("", "Probie IPMI finished")
+ SetTaskComplete(self, nil)
+ return nil
+}
diff --git a/pkg/baremetal/utils/ipmitool/ipmitool.go b/pkg/baremetal/utils/ipmitool/ipmitool.go
index 1c9cb51897..2ebfe5afd8 100644
--- a/pkg/baremetal/utils/ipmitool/ipmitool.go
+++ b/pkg/baremetal/utils/ipmitool/ipmitool.go
@@ -34,7 +34,6 @@ import (
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/ssh"
"yunion.io/x/onecloud/pkg/util/stringutils2"
- stage_stringutils "yunion.io/x/onecloud/pkg/util/stringutils2"
"yunion.io/x/onecloud/pkg/util/sysutils"
)
@@ -144,7 +143,8 @@ func GetSysInfo(exector IPMIExecutor) (*types.SIPMISystemInfo, error) {
args := []string{"fru", "print", "0"}
lines, err := exector.ExecuteCommand(args...)
if err != nil {
- return nil, err
+ // ignore error
+ log.Errorf("fru print 0 error: %s", err)
}
ret := make(map[string]string)
@@ -215,6 +215,9 @@ func GetLanConfig(exector IPMIExecutor, channel int) (*types.SIPMILanConfig, err
ret.Mac, _ = net.ParseMAC(val)
case "Default Gateway IP":
ret.Gateway = val
+ case "802.1q VLAN ID":
+ vlanId, _ := strconv.ParseInt(val, 10, 64)
+ ret.VlanId = int(vlanId)
}
}
return ret, nil
@@ -352,7 +355,7 @@ func CreateOrSetAdminUser(exector IPMIExecutor, channel int, rootId int, usernam
func SetLanUserAdminPasswd(exector IPMIExecutor, channel int, id int, password string) error {
var err error
- password, err = stage_stringutils.EscapeEchoString(password)
+ password, err = stringutils2.EscapeEchoString(password)
if err != nil {
return fmt.Errorf("EscapeEchoString for password: %s, error: %v", password, err)
}
diff --git a/pkg/cloudcommon/agent/agent.go b/pkg/cloudcommon/agent/agent.go
index 14e6f24375..57fc34341c 100644
--- a/pkg/cloudcommon/agent/agent.go
+++ b/pkg/cloudcommon/agent/agent.go
@@ -21,9 +21,11 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/version"
"yunion.io/x/onecloud/pkg/cloudcommon/object"
+ "yunion.io/x/onecloud/pkg/hostman/storageman"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
@@ -55,6 +57,9 @@ type SBaseAgent struct {
AgentName string
Zone *SZoneInfo
+ CachePath string
+ CacheManager *storageman.SLocalImageCacheManager
+
stop bool
}
@@ -78,7 +83,7 @@ func (agent *SBaseAgent) IAgent() IAgent {
return agent.GetVirtualObject().(IAgent)
}
-func (agent *SBaseAgent) Init(iagent IAgent, ifname string) error {
+func (agent *SBaseAgent) Init(iagent IAgent, ifname string, cachePath string) error {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return err
@@ -105,6 +110,7 @@ func (agent *SBaseAgent) Init(iagent IAgent, ifname string) error {
agent.SetVirtualObject(iagent)
agent.ListenInterface = iface
agent.ListenIPs = ips
+ agent.CachePath = cachePath
return nil
}
@@ -259,7 +265,7 @@ func (agent *SBaseAgent) createOrUpdateBaremetalAgent(session *mcclient.ClientSe
} else {
cloudBmAgent := ret.Data[0]
agentId, _ := cloudBmAgent.GetString("id")
- cloudObj, err = agent.updateBaremetalAgent(session, agentId)
+ cloudObj, err = agent.updateBaremetalAgent(session, agentId, "")
if err != nil {
return err
}
@@ -276,6 +282,25 @@ func (agent *SBaseAgent) createOrUpdateBaremetalAgent(session *mcclient.ClientSe
agent.AgentId = agentId
agent.AgentName = agentName
+
+ storageCacheId, _ := cloudObj.GetString("storagecache_id")
+ if len(storageCacheId) == 0 {
+ storageCacheId, err = agent.createStorageCache(session)
+ if err != nil {
+ return err
+ }
+ _, err = agent.updateBaremetalAgent(session, agentId, storageCacheId)
+ if err != nil {
+ return err
+ }
+ } else {
+ err = agent.updateStorageCache(session, storageCacheId)
+ if err != nil {
+ return err
+ }
+ }
+ agent.CacheManager = storageman.NewLocalImageCacheManager(agent.IAgent(), agent.CachePath, storageCacheId)
+
return nil
}
@@ -298,13 +323,17 @@ func (agent *SBaseAgent) GetListenUri() string {
}
func (agent *SBaseAgent) getCreateUpdateInfo() (jsonutils.JSONObject, error) {
- accessIP, err := agent.IAgent().GetAccessIP()
- if err != nil {
- return nil, err
- }
params := jsonutils.NewDict()
if agent.AgentId == "" {
- params.Add(jsonutils.NewString(fmt.Sprintf("%s_%s", agent.IAgent().GetAgentType(), accessIP)), "name")
+ agentName, err := agent.getName()
+ if err != nil {
+ return nil, errors.Wrap(err, "agent.getName")
+ }
+ params.Add(jsonutils.NewString(agentName), "name")
+ }
+ accessIP, err := agent.IAgent().GetAccessIP()
+ if err != nil {
+ return nil, errors.Wrap(err, "agent.IAgent().GetAccessIP()")
}
params.Add(jsonutils.NewString(accessIP.String()), "access_ip")
params.Add(jsonutils.NewString(agent.GetManagerUri()), "manager_uri")
@@ -315,6 +344,45 @@ func (agent *SBaseAgent) getCreateUpdateInfo() (jsonutils.JSONObject, error) {
return params, nil
}
+func (agent *SBaseAgent) getName() (string, error) {
+ accessIP, err := agent.IAgent().GetAccessIP()
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%s-%s", agent.IAgent().GetAgentType(), accessIP), nil
+}
+
+func (agent *SBaseAgent) createStorageCache(session *mcclient.ClientSession) (string, error) {
+ body := jsonutils.NewDict()
+ agentName, err := agent.getName()
+ if err != nil {
+ return "", errors.Wrap(err, "agent.getName")
+ }
+ body.Set("name", jsonutils.NewString("imagecache-"+agentName))
+ body.Set("path", jsonutils.NewString(agent.CachePath))
+ body.Set("external_id", jsonutils.NewString(agent.AgentId))
+ sc, err := modules.Storagecaches.Create(session, body)
+ if err != nil {
+ return "", errors.Wrap(err, "modules.Storagecaches.Create")
+ }
+ storageCacheId, err := sc.GetString("id")
+ if err != nil {
+ return "", errors.Wrap(err, "sc.GetString id")
+ }
+ return storageCacheId, nil
+}
+
+func (agent *SBaseAgent) updateStorageCache(session *mcclient.ClientSession, storageCacheId string) error {
+ body := jsonutils.NewDict()
+ body.Set("path", jsonutils.NewString(agent.CachePath))
+ body.Set("external_id", jsonutils.NewString(agent.AgentId))
+ _, err := modules.Storagecaches.Update(session, storageCacheId, body)
+ if err != nil {
+ return errors.Wrap(err, "modules.Storagecaches.Update")
+ }
+ return nil
+}
+
func (agent *SBaseAgent) createBaremetalAgent(session *mcclient.ClientSession) (jsonutils.JSONObject, error) {
params, err := agent.getCreateUpdateInfo()
if err != nil {
@@ -323,10 +391,17 @@ func (agent *SBaseAgent) createBaremetalAgent(session *mcclient.ClientSession) (
return modules.Baremetalagents.Create(session, params)
}
-func (agent *SBaseAgent) updateBaremetalAgent(session *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) {
- params, err := agent.getCreateUpdateInfo()
- if err != nil {
- return nil, err
+func (agent *SBaseAgent) updateBaremetalAgent(session *mcclient.ClientSession, id string, storageCacheId string) (jsonutils.JSONObject, error) {
+ var params jsonutils.JSONObject
+ var err error
+ if len(storageCacheId) > 0 {
+ params = jsonutils.NewDict()
+ params.(*jsonutils.JSONDict).Set("storagecache_id", jsonutils.NewString(storageCacheId))
+ } else {
+ params, err = agent.getCreateUpdateInfo()
+ if err != nil {
+ return nil, err
+ }
}
return modules.Baremetalagents.Update(session, id, params)
}
diff --git a/pkg/cloudcommon/agent/storagecache.go b/pkg/cloudcommon/agent/storagecache.go
new file mode 100644
index 0000000000..96f5bac128
--- /dev/null
+++ b/pkg/cloudcommon/agent/storagecache.go
@@ -0,0 +1,59 @@
+// 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 agent
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "yunion.io/x/onecloud/pkg/appsrv"
+ "yunion.io/x/onecloud/pkg/cloudcommon/workmanager"
+ "yunion.io/x/onecloud/pkg/hostman/hostutils"
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/mcclient/auth"
+)
+
+func (agent *SBaseAgent) 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.CacheManager.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.CacheManager.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)
+}
diff --git a/pkg/cloudcommon/db/external.go b/pkg/cloudcommon/db/external.go
index 50dade4376..88516cd218 100644
--- a/pkg/cloudcommon/db/external.go
+++ b/pkg/cloudcommon/db/external.go
@@ -23,7 +23,7 @@ import (
)
type SExternalizedResourceBase struct {
- ExternalId string `width:"256" charset:"utf8" index:"true" list:"user" create:"admin_optional"`
+ ExternalId string `width:"256" charset:"utf8" index:"true" list:"user" create:"admin_optional" update:"admin"`
}
func (model SExternalizedResourceBase) GetExternalId() string {
diff --git a/pkg/cloudcommon/options/options.go b/pkg/cloudcommon/options/options.go
index acc2ede99e..188f355e2c 100644
--- a/pkg/cloudcommon/options/options.go
+++ b/pkg/cloudcommon/options/options.go
@@ -70,6 +70,8 @@ type BaseOptions struct {
NonDefaultDomainProjects bool `help:"allow projects in non-default domains" default:"false"`
+ TimeZone string `help:"time zone" default:"Asia/Shanghai"`
+
structarg.BaseOptions
}
diff --git a/pkg/cloudcommon/types/ipmi.go b/pkg/cloudcommon/types/ipmi.go
index 3928139312..b17095efc0 100644
--- a/pkg/cloudcommon/types/ipmi.go
+++ b/pkg/cloudcommon/types/ipmi.go
@@ -22,11 +22,15 @@ const (
)
type SIPMIInfo struct {
- Username string `json:"username"`
- Password string `json:"password"`
- IpAddr string `json:"ip_addr"`
- Present bool `json:"present"`
- LanChannel int `json:"lan_channel"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ IpAddr string `json:"ip_addr,omitempty"`
+ Present bool `json:"present,omitempty"`
+ LanChannel int `json:"lan_channel,omitzero"`
+ Verified bool `json:"verified,omitfalse"`
+ RedfishApi bool `json:"redfish_api,omitfalse"`
+ CdromBoot bool `json:"cdrom_boot,omitfalse"`
+ PxeBoot bool `json:"pxe_boot,omitfalse"`
}
func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject {
@@ -42,5 +46,17 @@ func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject {
}
data.Add(jsonutils.NewBool(info.Present), "ipmi_present")
data.Add(jsonutils.NewInt(int64(info.LanChannel)), "ipmi_lan_channel")
+ if info.Verified {
+ data.Add(jsonutils.JSONTrue, "ipmi_verified")
+ }
+ if info.RedfishApi {
+ data.Add(jsonutils.JSONTrue, "ipmi_redfish_api")
+ }
+ if info.CdromBoot {
+ data.Add(jsonutils.JSONTrue, "ipmi_cdrom_boot")
+ }
+ if info.PxeBoot {
+ data.Add(jsonutils.JSONTrue, "ipmi_pxe_boot")
+ }
return data
}
diff --git a/pkg/cloudcommon/types/types.go b/pkg/cloudcommon/types/types.go
index c8f79888d4..82c812aabb 100644
--- a/pkg/cloudcommon/types/types.go
+++ b/pkg/cloudcommon/types/types.go
@@ -93,6 +93,9 @@ type SIPMILanConfig struct {
Netmask string `json:"netmask"`
Mac net.HardwareAddr `json:"mac"`
Gateway string `json:"gateway"`
+
+ VlanId int `json:"vlan_id"`
+ SpeedMbps int `json:"speed_mbps"`
}
type SIPMIBootFlags struct {
diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go
index 41fc078c6c..c6b7eb1299 100644
--- a/pkg/compute/guestdrivers/baremetals.go
+++ b/pkg/compute/guestdrivers/baremetals.go
@@ -120,7 +120,7 @@ func (self *SBaremetalGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk
return httperrors.NewUnsupportOperationError("Cannot resize disk for baremtal")
}
-func (self *SBaremetalGuestDriver) GetNamedNetworkConfiguration(guest *models.SGuest, userCred mcclient.TokenCredential, host *models.SHost, netConfig *api.NetworkConfig) (*models.SNetwork, []models.SNicConfig, models.IPAddlocationDirection) {
+func (self *SBaremetalGuestDriver) GetNamedNetworkConfiguration(guest *models.SGuest, userCred mcclient.TokenCredential, host *models.SHost, netConfig *api.NetworkConfig) (*models.SNetwork, []models.SNicConfig, api.IPAllocationDirection) {
netifs, net := host.GetNetinterfacesWithIdAndCredential(netConfig.Network, userCred, netConfig.Reserved)
if netifs != nil {
nicCnt := 1
@@ -142,7 +142,7 @@ func (self *SBaremetalGuestDriver) GetNamedNetworkConfiguration(guest *models.SG
}
nicConfs = append(nicConfs, nicConf)
}
- return net, nicConfs, models.IPAllocationStepup
+ return net, nicConfs, api.IPAllocationStepup
}
return net, nil, ""
}
@@ -214,7 +214,7 @@ func (self *SBaremetalGuestDriver) Attach2RandomNetwork(guest *models.SGuest, ct
}
nicConfs = append(nicConfs, nicConf)
}
- return guest.Attach2Network(ctx, userCred, net, pendingUsage, "", netConfig.Driver, netConfig.BwLimit, netConfig.Vip, false, models.IPAllocationStepup, false, nicConfs)
+ return guest.Attach2Network(ctx, userCred, net, pendingUsage, "", netConfig.Driver, netConfig.BwLimit, netConfig.Vip, false, api.IPAllocationStepup, false, nicConfs)
}
return nil, fmt.Errorf("No appropriate host virtual network...")
}
@@ -237,8 +237,24 @@ func (self *SBaremetalGuestDriver) ChooseHostStorage(host *models.SHost, backend
}
func (self *SBaremetalGuestDriver) RequestGuestCreateAllDisks(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
- task.ScheduleRun(nil) // skip
- return nil
+ diskCat := guest.CategorizeDisks()
+ var imageId string
+ if diskCat.Root != nil {
+ imageId = diskCat.Root.GetTemplateId()
+ }
+ if len(imageId) == 0 {
+ task.ScheduleRun(nil)
+ return nil
+ }
+ storage := diskCat.Root.GetStorage()
+ if storage == nil {
+ return fmt.Errorf("no valid storage")
+ }
+ storageCache := storage.GetStoragecache()
+ if storageCache == nil {
+ return fmt.Errorf("no valid storage cache")
+ }
+ return storageCache.StartImageCacheTask(ctx, task.GetUserCred(), imageId, diskCat.Root.DiskFormat, false, task.GetTaskId())
}
func (self *SBaremetalGuestDriver) RequestGuestCreateInsertIso(ctx context.Context, imageId string, guest *models.SGuest, task taskman.ITask) error {
diff --git a/pkg/compute/guestdrivers/virtualization.go b/pkg/compute/guestdrivers/virtualization.go
index 8d9a7168f9..990ba5634c 100644
--- a/pkg/compute/guestdrivers/virtualization.go
+++ b/pkg/compute/guestdrivers/virtualization.go
@@ -52,7 +52,7 @@ func (self *SVirtualizedGuestDriver) PrepareDiskRaidConfig(userCred mcclient.Tok
return nil
}
-func (self *SVirtualizedGuestDriver) GetNamedNetworkConfiguration(guest *models.SGuest, userCred mcclient.TokenCredential, host *models.SHost, netConfig *api.NetworkConfig) (*models.SNetwork, []models.SNicConfig, models.IPAddlocationDirection) {
+func (self *SVirtualizedGuestDriver) GetNamedNetworkConfiguration(guest *models.SGuest, userCred mcclient.TokenCredential, host *models.SHost, netConfig *api.NetworkConfig) (*models.SNetwork, []models.SNicConfig, api.IPAllocationDirection) {
net, _ := host.GetNetworkWithIdAndCredential(netConfig.Network, userCred, netConfig.Reserved)
nicConfs := []models.SNicConfig{
{
@@ -68,7 +68,7 @@ func (self *SVirtualizedGuestDriver) GetNamedNetworkConfiguration(guest *models.
Ifname: "",
})
}
- return net, nicConfs, models.IPAllocationStepdown
+ return net, nicConfs, api.IPAllocationStepdown
}
func (self *SVirtualizedGuestDriver) GetRandomNetworkTypes() []string {
@@ -147,7 +147,7 @@ func (self *SVirtualizedGuestDriver) Attach2RandomNetwork(guest *models.SGuest,
}
nicConfs = append(nicConfs, nicConf)
}
- gn, err := guest.Attach2Network(ctx, userCred, selNet, pendingUsage, netConfig.Address, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, netConfig.Reserved, models.IPAllocationDefault, netConfig.RequireDesignatedIP, nicConfs)
+ gn, err := guest.Attach2Network(ctx, userCred, selNet, pendingUsage, netConfig.Address, netConfig.Driver, netConfig.BwLimit, netConfig.Vip, netConfig.Reserved, api.IPAllocationDefault, netConfig.RequireDesignatedIP, nicConfs)
return gn, err
}
diff --git a/pkg/compute/hostdrivers/baremetal.go b/pkg/compute/hostdrivers/baremetal.go
index 2153611f48..f1af16692f 100644
--- a/pkg/compute/hostdrivers/baremetal.go
+++ b/pkg/compute/hostdrivers/baremetal.go
@@ -43,7 +43,41 @@ func (self *SBaremetalHostDriver) GetHypervisor() string {
}
func (self *SBaremetalHostDriver) CheckAndSetCacheImage(ctx context.Context, host *models.SHost, storageCache *models.SStoragecache, task taskman.ITask) error {
- return fmt.Errorf("not supported")
+ params := task.GetParams()
+ imageId, err := params.GetString("image_id")
+ if err != nil {
+ return err
+ }
+ _, err = models.CachedimageManager.FetchById(imageId)
+ if err != nil {
+ return err
+ }
+ format, _ := params.GetString("format")
+ isForce := jsonutils.QueryBoolean(params, "is_force", false)
+
+ type contentStruct struct {
+ ImageId string
+ Format string
+ IsForce bool
+ }
+
+ content := contentStruct{}
+ content.ImageId = imageId
+ content.Format = format
+ if isForce {
+ content.IsForce = true
+ }
+
+ url := "/disks/image_cache"
+ body := jsonutils.NewDict()
+ body.Add(jsonutils.Marshal(&content), "disk")
+
+ header := task.GetTaskRequestHeader()
+ _, err = host.BaremetalSyncRequest(ctx, "POST", url, header, body)
+ if err != nil {
+ return err
+ }
+ return nil
}
func (self *SBaremetalHostDriver) RequestAllocateDiskOnStorage(ctx context.Context, host *models.SHost, storage *models.SStorage, disk *models.SDisk, task taskman.ITask, content *jsonutils.JSONDict) error {
diff --git a/pkg/compute/models/baremetalagents.go b/pkg/compute/models/baremetalagents.go
index f619513bb7..7b4616a227 100644
--- a/pkg/compute/models/baremetalagents.go
+++ b/pkg/compute/models/baremetalagents.go
@@ -44,6 +44,8 @@ type SBaremetalagent struct {
AgentType string `width:"32" charset:"ascii" nullable:"true" default:"baremetal" list:"admin" update:"admin" create:"admin_optional"`
Version string `width:"64" charset:"ascii" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(64, charset='ascii'))
+
+ StoragecacheId string `width:"36" charset:"ascii" nullable:"true" list:"admin" get:"admin" update:"admin" create:"admin_optional"`
}
var BaremetalagentManager *SBaremetalagentManager
@@ -82,6 +84,13 @@ func (self *SBaremetalagent) ValidateDeleteCondition(ctx context.Context) error
if self.Status == api.BAREMETAL_AGENT_ENABLED {
return fmt.Errorf("Cannot delete in status %s", self.Status)
}
+ storageCache, _ := self.getStorageCache()
+ if storageCache != nil {
+ err := storageCache.ValidateDeleteCondition(ctx)
+ if err != nil {
+ return fmt.Errorf("storagecache cannot be delete: %s", err)
+ }
+ }
return self.SStandaloneResourceBase.ValidateDeleteCondition(ctx)
}
@@ -211,3 +220,37 @@ func (manager *SBaremetalagentManager) GetAgent(agentType api.TAgentType, zoneId
}
return &agents[0]
}
+
+func (cache *SBaremetalagent) getStorageCache() (*SStoragecache, error) {
+ if len(cache.StoragecacheId) > 0 {
+ cacheObj, err := StoragecacheManager.FetchById(cache.StoragecacheId)
+ if err != nil {
+ return nil, err
+ }
+ return cacheObj.(*SStoragecache), nil
+ }
+ return nil, nil
+}
+
+func (agent *SBaremetalagent) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
+ err := agent.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data)
+ if err != nil {
+ return err
+ }
+ cache, _ := agent.getStorageCache()
+ if cache != nil {
+ err = cache.Delete(ctx, userCred)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (agent *SBaremetalagent) setStoragecacheId(cacheId string) error {
+ _, err := db.Update(agent, func() error {
+ agent.StoragecacheId = cacheId
+ return nil
+ })
+ return err
+}
diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go
index d6c154e1ee..7315eae918 100644
--- a/pkg/compute/models/guest_actions.go
+++ b/pkg/compute/models/guest_actions.go
@@ -4045,3 +4045,21 @@ func (manager *SGuestManager) CreateGuestFromInstanceSnapshot(
guest := iGuest.(*SGuest)
return guest, guestParams, nil
}
+
+func (self *SGuest) AllowGetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
+ return self.IsOwner(userCred) || db.IsAdminAllowGetSpec(userCred, self, "jnlp")
+}
+
+func (self *SGuest) GetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
+ if self.Hypervisor != api.HYPERVISOR_BAREMETAL {
+ return nil, httperrors.NewInvalidStatusError("not a baremetal server")
+ }
+ host := self.GetHost()
+ if host == nil {
+ return nil, httperrors.NewInvalidStatusError("no valid host")
+ }
+ if !host.IsBaremetal {
+ return nil, httperrors.NewInvalidStatusError("host is not a baremetal")
+ }
+ return host.GetDetailsJnlp(ctx, userCred, query)
+}
diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go
index e2f88d5caa..b453f5246f 100644
--- a/pkg/compute/models/guestdrivers.go
+++ b/pkg/compute/models/guestdrivers.go
@@ -64,7 +64,7 @@ type IGuestDriver interface {
PrepareDiskRaidConfig(userCred mcclient.TokenCredential, host *SHost, params []*api.BaremetalDiskConfig) error
- GetNamedNetworkConfiguration(guest *SGuest, userCred mcclient.TokenCredential, host *SHost, netConfig *api.NetworkConfig) (*SNetwork, []SNicConfig, IPAddlocationDirection)
+ GetNamedNetworkConfiguration(guest *SGuest, userCred mcclient.TokenCredential, host *SHost, netConfig *api.NetworkConfig) (*SNetwork, []SNicConfig, api.IPAllocationDirection)
Attach2RandomNetwork(guest *SGuest, ctx context.Context, userCred mcclient.TokenCredential, host *SHost, netConfig *api.NetworkConfig, pendingUsage quotas.IQuota) ([]SGuestnetwork, error)
GetRandomNetworkTypes() []string
diff --git a/pkg/compute/models/guestnetworks.go b/pkg/compute/models/guestnetworks.go
index d5ca2a51dc..9ca4832b15 100644
--- a/pkg/compute/models/guestnetworks.go
+++ b/pkg/compute/models/guestnetworks.go
@@ -151,7 +151,7 @@ func (manager *SGuestnetworkManager) GenerateMac(netId string, suggestion string
func (manager *SGuestnetworkManager) newGuestNetwork(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, network *SNetwork,
index int8, address string, mac string, driver string, bwLimit int, virtual bool, reserved bool,
- allocDir IPAddlocationDirection, requiredDesignatedIp bool, ifName string, teamWithMac string) (*SGuestnetwork, error) {
+ allocDir api.IPAllocationDirection, requiredDesignatedIp bool, ifName string, teamWithMac string) (*SGuestnetwork, error) {
gn := SGuestnetwork{}
gn.SetModelManager(GuestnetworkManager, &gn)
diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go
index 52505b448d..b09d31aede 100644
--- a/pkg/compute/models/guests.go
+++ b/pkg/compute/models/guests.go
@@ -2329,7 +2329,7 @@ func (self *SGuest) Attach2Network(ctx context.Context, userCred mcclient.TokenC
pendingUsage quotas.IQuota,
address string,
driver string, bwLimit int, virtual bool,
- reserved bool, allocDir IPAddlocationDirection, requireDesignatedIP bool,
+ reserved bool, allocDir api.IPAllocationDirection, requireDesignatedIP bool,
nicConfs []SNicConfig) ([]SGuestnetwork, error) {
firstNic, err := self.attach2NetworkOnce(ctx, userCred, network, pendingUsage, address, driver, bwLimit, virtual,
@@ -2359,7 +2359,7 @@ func (self *SGuest) attach2NetworkOnce(ctx context.Context, userCred mcclient.To
pendingUsage quotas.IQuota,
address string,
driver string, bwLimit int, virtual bool,
- reserved bool, allocDir IPAddlocationDirection, requireDesignatedIP bool,
+ reserved bool, allocDir api.IPAllocationDirection, requireDesignatedIP bool,
nicConf SNicConfig, teamWithMac string) (*SGuestnetwork, error) {
/*
allow a guest attach to a network 2 times
@@ -2539,7 +2539,7 @@ func (self *SGuest) SyncVMNics(ctx context.Context, userCred mcclient.TokenCrede
Ifname: "",
}
_, err = self.Attach2Network(ctx, userCred, add.net, nil, ipStr,
- add.nic.GetDriver(), 0, false, add.reserve, IPAllocationDefault, true, []SNicConfig{nicConf})
+ add.nic.GetDriver(), 0, false, add.reserve, api.IPAllocationDefault, true, []SNicConfig{nicConf})
if err != nil {
result.AddError(err)
} else {
diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go
index 4e64eb547f..279d5d4117 100644
--- a/pkg/compute/models/hosts.go
+++ b/pkg/compute/models/hosts.go
@@ -42,6 +42,7 @@ import (
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
+ "yunion.io/x/onecloud/pkg/cloudcommon/types"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/baremetal"
"yunion.io/x/onecloud/pkg/compute/options"
@@ -51,6 +52,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/logclient"
+ "yunion.io/x/onecloud/pkg/util/redfish/bmconsole"
"yunion.io/x/onecloud/pkg/util/seclib2"
)
@@ -83,9 +85,11 @@ type SHost struct {
Rack string `width:"16" charset:"ascii" nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True)
Slots string `width:"16" charset:"ascii" nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True)
- AccessMac string `width:"32" charset:"ascii" nullable:"false" index:"true" list:"admin" update:"admin" create:"admin_required"` // Column(VARCHAR(32, charset='ascii'), nullable=False, index=True)
- AccessIp string `width:"16" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True)
- ManagerUri string `width:"256" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(256, charset='ascii'), nullable=True)
+ AccessMac string `width:"32" charset:"ascii" nullable:"false" index:"true" list:"admin" update:"admin"` // Column(VARCHAR(32, charset='ascii'), nullable=False, index=True)
+
+ AccessIp string `width:"16" charset:"ascii" nullable:"true" list:"admin"` // Column(VARCHAR(16, charset='ascii'), nullable=True)
+
+ ManagerUri string `width:"256" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(256, charset='ascii'), nullable=True)
SysInfo jsonutils.JSONObject `nullable:"true" search:"admin" list:"admin" update:"admin" create:"admin_optional"` // Column(JSONEncodedDict, nullable=True)
SN string `width:"128" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(128, charset='ascii'), nullable=True)
@@ -108,6 +112,8 @@ type SHost struct {
StorageDriver string `width:"20" charset:"ascii" nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(20, charset='ascii'), nullable=True)
StorageInfo jsonutils.JSONObject `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(JSONEncodedDict, nullable=True)
+ IpmiIp string `width:"16" charset:"ascii" nullable:"true" list:"admin"` // Column(VARCHAR(16, charset='ascii'), nullable=True)
+
IpmiInfo jsonutils.JSONObject `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(JSONEncodedDict, nullable=True)
// Status string = Column(VARCHAR(16, charset='ascii'), nullable=False, default=baremetalstatus.INIT) # status
@@ -130,6 +136,8 @@ type SHost struct {
RealExternalId string `width:"256" charset:"utf8" get:"admin"`
IsImport bool `nullable:"true" default:"false" list:"admin" create:"admin_optional"`
+
+ EnablePxeBoot tristate.TriState `nullable:"false" default:"true" list:"admin" create:"admin_optional" update:"admin"`
}
func (manager *SHostManager) GetContextManagers() [][]db.IModelManager {
@@ -652,6 +660,7 @@ func (self *SHost) PerformUpdateStorage(
bs := self.GetBaremetalstorage()
capacity, _ := data.Int("capacity")
zoneId, _ := data.GetString("zone_id")
+ storageCacheId, _ := data.GetString("storagecache_id")
if bs == nil {
// 1. create storage
storage := SStorage{}
@@ -662,6 +671,7 @@ func (self *SHost) PerformUpdateStorage(
storage.Cmtbound = 1.0
storage.Status = api.STORAGE_ONLINE
storage.ZoneId = zoneId
+ storage.StoragecacheId = storageCacheId
err := StorageManager.TableSpec().Insert(&storage)
if err != nil {
return nil, fmt.Errorf("Create baremetal storage error: %v", err)
@@ -683,16 +693,17 @@ func (self *SHost) PerformUpdateStorage(
return nil, nil
}
storage := bs.GetStorage()
- if capacity != int64(storage.Capacity) {
- diff, err := db.Update(storage, func() error {
- storage.Capacity = capacity
- return nil
- })
- if err != nil {
- return nil, fmt.Errorf("Update baremetal storage error: %v", err)
- }
- db.OpsLog.LogEvent(storage, db.ACT_UPDATE, diff, userCred)
+ //if capacity != int64(storage.Capacity) {
+ diff, err := db.Update(storage, func() error {
+ storage.Capacity = capacity
+ storage.StoragecacheId = storageCacheId
+ return nil
+ })
+ if err != nil {
+ return nil, fmt.Errorf("Update baremetal storage error: %v", err)
}
+ db.OpsLog.LogEvent(storage, db.ACT_UPDATE, diff, userCred)
+ //}
return nil, nil
}
@@ -2429,8 +2440,34 @@ func (self *SHost) PostCreate(ctx context.Context, userCred mcclient.TokenCreden
})
if err != nil {
log.Errorln(err.Error())
+ } else {
+ ipmiIp, _ := ipmiInfo.GetString("ip_addr")
+ if len(ipmiIp) > 0 {
+ self.setIpmiIp(userCred, ipmiIp)
+ }
}
}
+ accessIp, _ := data.GetString("access_ip")
+ if len(accessIp) > 0 {
+ self.setAccessIp(userCred, accessIp)
+ }
+ accessMac, _ := data.GetString("access_mac")
+ if len(accessMac) > 0 {
+ self.setAccessMac(userCred, accessMac)
+ }
+ if len(self.ZoneId) > 0 && self.HostType == api.HOST_TYPE_BAREMETAL {
+ self.StartBaremetalCreateTask(ctx, userCred, kwargs, "")
+ }
+}
+
+func (self *SHost) StartBaremetalCreateTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error {
+ if task, err := taskman.TaskManager.NewTask(ctx, "BaremetalCreateTask", self, userCred, data, parentTaskId, "", nil); err != nil {
+ log.Errorln(err)
+ return err
+ } else {
+ task.ScheduleRun(nil)
+ return nil
+ }
}
func (manager *SHostManager) ValidateSizeParams(data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
@@ -2470,14 +2507,14 @@ func (manager *SHostManager) ValidateSizeParams(data *jsonutils.JSONDict) (*json
return data, nil
}
-func inputUniquenessCheck(data *jsonutils.JSONDict, zoneId string, hostId string) (*jsonutils.JSONDict, error) {
+func (manager *SHostManager) inputUniquenessCheck(data *jsonutils.JSONDict, zoneId string, hostId string) (*jsonutils.JSONDict, error) {
for _, key := range []string{
"manager_uri",
"access_ip",
} {
val, _ := data.GetString(key)
if len(val) > 0 {
- q := HostManager.Query().Equals(key, val)
+ q := manager.Query().Equals(key, val)
if len(zoneId) > 0 {
q = q.Equals("zone_id", zoneId)
} else {
@@ -2502,25 +2539,28 @@ func inputUniquenessCheck(data *jsonutils.JSONDict, zoneId string, hostId string
if len(accessMac2) == 0 {
return nil, httperrors.NewInputParameterError("invalid macAddr %s", accessMac)
}
- q := HostManager.Query().Equals("access_mac", accessMac2)
- if len(hostId) > 0 {
- q = q.NotEquals("id", hostId)
+ if accessMac2 != api.ACCESS_MAC_ANY {
+ q := manager.Query().Equals("access_mac", accessMac2)
+ if len(hostId) > 0 {
+ q = q.NotEquals("id", hostId)
+ }
+ cnt, err := q.CountWithError()
+ if err != nil {
+ return nil, httperrors.NewInternalServerError("check access_mac duplication fail %s", err)
+ }
+ if cnt > 0 {
+ return nil, httperrors.NewConflictError("duplicate access_mac %s", accessMac)
+ }
+ data.Set("access_mac", jsonutils.NewString(accessMac2))
}
- cnt, err := q.CountWithError()
- if err != nil {
- return nil, httperrors.NewInternalServerError("check access_mac duplication fail %s", err)
- }
- if cnt > 0 {
- return nil, httperrors.NewConflictError("duplicate access_mac %s", accessMac)
- }
- data.Set("access_mac", jsonutils.NewString(accessMac2))
}
return data, nil
}
func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
- zoneId := jsonutils.GetAnyString(data, []string{"zone_id", "zone"})
+ zoneId, zoneKey := jsonutils.GetAnyString2(data, []string{"zone_id", "zone"})
if len(zoneId) > 0 {
+ data.Remove(zoneKey)
zoneObj, err := ZoneManager.FetchByIdOrName(userCred, zoneId)
if err != nil {
if err == sql.ErrNoRows {
@@ -2533,7 +2573,7 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc
data.Set("zone_id", jsonutils.NewString(zoneObj.GetId()))
}
- data, err := inputUniquenessCheck(data, zoneId, "")
+ data, err := manager.inputUniquenessCheck(data, zoneId, "")
if err != nil {
return nil, err
}
@@ -2562,18 +2602,116 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc
return nil, httperrors.NewInputParameterError("%s", err)
}
ipmiIpAddr, _ := ipmiInfo.GetString("ip_addr")
- if len(ipmiIpAddr) > 0 && !NetworkManager.IsValidOnPremiseNetworkIP(ipmiIpAddr) {
- return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", ipmiIpAddr)
+ if len(ipmiIpAddr) > 0 {
+ net, _ := NetworkManager.GetOnPremiseNetworkOfIP(ipmiIpAddr, "", tristate.None)
+ if net == nil {
+ return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", ipmiIpAddr)
+ }
+ // reserve this IP temporarily
+ err := net.reserveIpWithDuration(ctx, userCred, ipmiIpAddr, "reserve for baremetal ipmi IP", 30*time.Minute)
+ if err != nil {
+ return nil, err
+ }
+ zoneObj := net.getZone()
+ if zoneObj == nil {
+ return nil, httperrors.NewInputParameterError("IPMI network has no zone???")
+ }
+ originZoneId, _ := data.GetString("zone_id")
+ if len(originZoneId) > 0 && originZoneId != zoneObj.GetId() {
+ return nil, httperrors.NewInputParameterError("IPMI address located in different zone than specified")
+ }
+ data.Set("zone_id", jsonutils.NewString(zoneObj.GetId()))
+ }
+ var accessNet *SNetwork
+ accessIpAddr, _ := data.GetString("access_ip")
+ if len(accessIpAddr) > 0 {
+ net, _ := NetworkManager.GetOnPremiseNetworkOfIP(accessIpAddr, "", tristate.None)
+ if net == nil {
+ return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", accessIpAddr)
+ }
+ accessNet = net
+ } else {
+ accessNetStr, _ := data.GetString("access_net")
+ if len(accessNetStr) > 0 {
+ netObj, err := NetworkManager.FetchByIdOrName(userCred, accessNetStr)
+ if err != nil {
+ if errors.Cause(err) == sql.ErrNoRows {
+ return nil, httperrors.NewResourceNotFoundError2("network", accessNetStr)
+ } else {
+ return nil, httperrors.NewGeneralError(err)
+ }
+ }
+ accessNet = netObj.(*SNetwork)
+ } else {
+ accessWireStr, _ := data.GetString("access_wire")
+ if len(accessWireStr) > 0 {
+ wireObj, err := WireManager.FetchByIdOrName(userCred, accessWireStr)
+ if err != nil {
+ if errors.Cause(err) == sql.ErrNoRows {
+ return nil, httperrors.NewResourceNotFoundError2("wire", accessWireStr)
+ } else {
+ return nil, httperrors.NewGeneralError(err)
+ }
+ }
+ wire := wireObj.(*SWire)
+ lockman.LockObject(ctx, wire)
+ defer lockman.ReleaseObject(ctx, wire)
+ net, err := wire.GetCandidatePrivateNetwork(userCred, false, []string{api.NETWORK_TYPE_PXE, api.NETWORK_TYPE_BAREMETAL, api.NETWORK_TYPE_GUEST})
+ if err != nil {
+ return nil, httperrors.NewGeneralError(err)
+ }
+ accessNet = net
+ }
+ }
+ }
+ if accessNet != nil {
+ lockman.LockObject(ctx, accessNet)
+ defer lockman.ReleaseObject(ctx, accessNet)
+
+ accessIp, err := accessNet.GetFreeIP(ctx, userCred, nil, nil, accessIpAddr, api.IPAllocationNone, false)
+ if err != nil {
+ return nil, httperrors.NewGeneralError(err)
+ }
+
+ if len(accessIpAddr) > 0 && accessIpAddr != accessIp {
+ return nil, httperrors.NewConflictError("Access ip %s has been used", accessIpAddr)
+ }
+
+ zoneObj := accessNet.getZone()
+ if zoneObj == nil {
+ return nil, httperrors.NewInputParameterError("Access network has no zone???")
+ }
+ originZoneId, _ := data.GetString("zone_id")
+ if len(originZoneId) > 0 && originZoneId != zoneObj.GetId() {
+ return nil, httperrors.NewInputParameterError("Access address located in different zone than specified")
+ }
+
+ // reserve this IP temporarily
+ err = accessNet.reserveIpWithDuration(ctx, userCred, accessIp, "reserve for baremetal access IP", 30*time.Minute)
+ if err != nil {
+ return nil, err
+ }
+ data.Set("access_ip", jsonutils.NewString(accessIp))
+ data.Set("zone_id", jsonutils.NewString(zoneObj.GetId()))
}
ipmiPasswd, _ := ipmiInfo.GetString("password")
if len(ipmiPasswd) > 0 && !seclib2.MeetComplxity(ipmiPasswd) {
return nil, httperrors.NewWeakPasswordError()
}
+ // only baremetal can be created
+ hostType, _ := data.GetString("host_type")
+ if len(hostType) == 0 {
+ hostType = api.HOST_TYPE_BAREMETAL
+ data.Set("host_type", jsonutils.NewString(hostType))
+ }
+ if hostType == api.HOST_TYPE_BAREMETAL {
+ data.Set("is_baremetal", jsonutils.JSONTrue)
+ }
return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data)
}
func (self *SHost) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
- data, err := inputUniquenessCheck(data, self.ZoneId, self.Id)
+ data, err := HostManager.inputUniquenessCheck(data, self.ZoneId, self.Id)
if err != nil {
return nil, err
}
@@ -2588,8 +2726,18 @@ func (self *SHost) ValidateUpdateData(ctx context.Context, userCred mcclient.Tok
}
if ipmiInfo.Length() > 0 {
ipmiIpAddr, _ := ipmiInfo.GetString("ip_addr")
- if len(ipmiIpAddr) > 0 && !NetworkManager.IsValidOnPremiseNetworkIP(ipmiIpAddr) {
- return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", ipmiIpAddr)
+ if len(ipmiIpAddr) > 0 {
+ net, _ := NetworkManager.GetOnPremiseNetworkOfIP(ipmiIpAddr, "", tristate.None)
+ if net == nil {
+ return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", ipmiIpAddr)
+ }
+ zoneObj := net.getZone()
+ if zoneObj == nil {
+ return nil, httperrors.NewInputParameterError("IPMI network has not zone???")
+ }
+ if zoneObj.GetId() != self.ZoneId {
+ return nil, httperrors.NewInputParameterError("New IPMI address located in another zone!")
+ }
}
val := jsonutils.NewDict()
val.Update(self.IpmiInfo)
@@ -2986,6 +3134,32 @@ func (self *SHost) StartPrepareTask(ctx context.Context, userCred mcclient.Token
}
}
+func (self *SHost) AllowPerformIpmiProbe(ctx context.Context,
+ userCred mcclient.TokenCredential,
+ query jsonutils.JSONObject,
+ data jsonutils.JSONObject) bool {
+ return db.IsAdminAllowPerform(userCred, self, "ipmi-probe")
+}
+
+func (self *SHost) PerformIpmiProbe(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
+ if utils.IsInStringArray(self.Status, []string{api.BAREMETAL_INIT, api.BAREMETAL_READY, api.BAREMETAL_RUNNING}) {
+ return nil, self.StartIpmiProbeTask(ctx, userCred, "")
+ }
+ return nil, httperrors.NewInvalidStatusError("Cannot do Ipmi-probe in status %s", self.Status)
+}
+
+func (self *SHost) StartIpmiProbeTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
+ data := jsonutils.NewDict()
+ self.SetStatus(userCred, api.BAREMETAL_START_PROBE, "start ipmi-probe task")
+ if task, err := taskman.TaskManager.NewTask(ctx, "BaremetalIpmiProbeTask", self, userCred, data, parentTaskId, "", nil); err != nil {
+ log.Errorln(err)
+ return err
+ } else {
+ task.ScheduleRun(nil)
+ return nil
+ }
+}
+
func (self *SHost) AllowPerformInitialize(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject,
@@ -3226,6 +3400,12 @@ func (self *SHost) addNetif(ctx context.Context, userCred mcclient.TokenCredenti
}
}
}
+ if netif.NicType == api.NIC_TYPE_ADMIN {
+ err := self.setAccessMac(userCred, netif.Mac)
+ if err != nil {
+ return httperrors.NewBadRequestError(err.Error())
+ }
+ }
if len(ipAddr) > 0 {
err = self.EnableNetif(ctx, userCred, netif, "", ipAddr, "", "", reserve, requireDesignatedIp)
if err != nil {
@@ -3322,7 +3502,17 @@ func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCrede
} else if net.WireId != wire.Id {
return fmt.Errorf("conflict??? candiate net is not on wire")
}
- return self.Attach2Network(ctx, userCred, netif, net, ipAddr, allocDir, reserve, requireDesignatedIp)
+ err = self.Attach2Network(ctx, userCred, netif, net, ipAddr, allocDir, reserve, requireDesignatedIp)
+ if err != nil {
+ return errors.Wrap(err, "self.Attach2Network")
+ }
+ switch netif.NicType {
+ case api.NIC_TYPE_IPMI:
+ err = self.setIpmiIp(userCred, ipAddr)
+ case api.NIC_TYPE_ADMIN:
+ err = self.setAccessIp(userCred, ipAddr)
+ }
+ return err
}
func (self *SHost) AllowPerformDisableNetif(ctx context.Context,
@@ -3348,19 +3538,31 @@ func (self *SHost) PerformDisableNetif(ctx context.Context, userCred mcclient.To
func (self *SHost) DisableNetif(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, reserve bool) error {
bn := netif.GetBaremetalNetwork()
+ var ipAddr string
if bn != nil {
+ ipAddr = bn.IpAddr
self.UpdateDnsRecord(netif, false)
self.DeleteBaremetalnetwork(ctx, userCred, bn, reserve)
}
- return nil
+ var err error
+ switch netif.NicType {
+ case api.NIC_TYPE_IPMI:
+ if ipAddr == self.IpmiIp {
+ err = self.setIpmiIp(userCred, "")
+ }
+ case api.NIC_TYPE_ADMIN:
+ if ipAddr == self.AccessIp {
+ err = self.setAccessIp(userCred, "")
+ }
+ }
+ return err
}
func (self *SHost) Attach2Network(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, net *SNetwork, ipAddr, allocDir string, reserved, requireDesignatedIp bool) error {
lockman.LockObject(ctx, net)
defer lockman.ReleaseObject(ctx, net)
- usedAddr := net.GetUsedAddresses()
- freeIp, err := net.GetFreeIP(ctx, userCred, usedAddr, nil, ipAddr, IPAddlocationDirection(allocDir), reserved)
+ freeIp, err := net.GetFreeIP(ctx, userCred, nil, nil, ipAddr, api.IPAllocationDirection(allocDir), reserved)
if err != nil {
log.Errorf("attach2network: %s", err)
return err
@@ -3407,19 +3609,25 @@ func (self *SHost) PerformRemoveNetif(ctx context.Context, userCred mcclient.Tok
func (self *SHost) RemoveNetif(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, reserve bool) error {
wire := netif.GetWire()
self.DisableNetif(ctx, userCred, netif, reserve)
- log.Infof("Remove wire")
+ nicType := netif.NicType
+ mac := netif.Mac
err := netif.Remove(ctx, userCred)
if err != nil {
- return err
+ return errors.Wrap(err, "netif.Remove")
+ }
+ if nicType == api.NIC_TYPE_ADMIN && self.AccessMac == mac {
+ err := self.setAccessMac(userCred, "")
+ if err != nil {
+ return errors.Wrap(err, "self.setAccessMac")
+ }
}
if wire != nil {
- log.Infof("Remove wire")
others := self.GetNetifsOnWire(wire)
if len(others) == 0 {
hw, _ := HostwireManager.FetchByHostIdAndMac(self.Id, netif.Mac)
if hw != nil {
db.OpsLog.LogDetachEvent(ctx, self, wire, userCred, jsonutils.NewString(fmt.Sprintf("disable netif %s", self.AccessMac)))
- log.Infof("Detach host wire because of remove netif %s", netif.Mac)
+ log.Debugf("Detach host wire because of remove netif %s", netif.Mac)
return hw.Delete(ctx, userCred)
}
}
@@ -4167,3 +4375,100 @@ func (host *SHost) InstanceGroups() ([]SGroup, map[string]int, error) {
}
return groups, groupSet, nil
}
+
+func (host *SHost) setIpmiIp(userCred mcclient.TokenCredential, ipAddr string) error {
+ if host.IpmiIp == ipAddr {
+ return nil
+ }
+ diff, err := db.Update(host, func() error {
+ host.IpmiIp = ipAddr
+ return nil
+ })
+ if err != nil {
+ return errors.Wrap(err, "db.Update")
+ }
+ db.OpsLog.LogEvent(host, db.ACT_UPDATE, diff, userCred)
+ return nil
+}
+
+func (host *SHost) setAccessIp(userCred mcclient.TokenCredential, ipAddr string) error {
+ if host.AccessIp == ipAddr {
+ return nil
+ }
+ diff, err := db.Update(host, func() error {
+ host.AccessIp = ipAddr
+ return nil
+ })
+ if err != nil {
+ return errors.Wrap(err, "db.Update")
+ }
+ db.OpsLog.LogEvent(host, db.ACT_UPDATE, diff, userCred)
+ return nil
+}
+
+func (host *SHost) setAccessMac(userCred mcclient.TokenCredential, mac string) error {
+ mac = netutils.FormatMacAddr(mac)
+ if host.AccessMac == mac {
+ return nil
+ }
+ diff, err := db.Update(host, func() error {
+ host.AccessMac = mac
+ return nil
+ })
+ if err != nil {
+ return errors.Wrap(err, "db.Update")
+ }
+ db.OpsLog.LogEvent(host, db.ACT_UPDATE, diff, userCred)
+ return nil
+}
+
+func (host *SHost) GetIpmiInfo() (types.SIPMIInfo, error) {
+ info := types.SIPMIInfo{}
+ if host.IpmiInfo != nil {
+ err := host.IpmiInfo.Unmarshal(&info)
+ if err != nil {
+ return info, errors.Wrap(err, "host.IpmiInfo.Unmarshal")
+ }
+ }
+ return info, nil
+}
+
+func (self *SHost) AllowGetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
+ return db.IsAdminAllowGetSpec(userCred, self, "jnlp")
+}
+
+func (self *SHost) GetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
+ ipmi, err := self.GetIpmiInfo()
+ if err != nil {
+ return nil, httperrors.NewInvalidStatusError("no valid ipmi_info")
+ }
+ if !ipmi.Verified {
+ return nil, httperrors.NewInvalidStatusError("no veried ipmi_info")
+ }
+ if self.SysInfo == nil {
+ return nil, httperrors.NewInvalidStatusError("no valid sys_info")
+ }
+ ipmiPass, err := utils.DescryptAESBase64(self.Id, ipmi.Password)
+ if err != nil {
+ return nil, httperrors.NewInternalServerError("decrypt ipmi password fail: %s", err)
+ }
+ bmc := bmconsole.NewBMCConsole(ipmi.IpAddr, ipmi.Username, ipmiPass, false)
+ manufacture, _ := self.SysInfo.GetString("manufacture")
+ var jnlp string
+ switch strings.ToLower(manufacture) {
+ case "hp", "hpe":
+ jnlp, err = bmc.GetIloConsoleJNLP(ctx)
+ case "dell":
+ sku, _ := self.SysInfo.GetString("sku")
+ model, _ := self.SysInfo.GetString("model")
+ jnlp, err = bmc.GetIdracConsoleJNLP(ctx, sku, model)
+ default:
+ return nil, httperrors.NewNotImplementedError("Unsupported manufacture %s", manufacture)
+ }
+ if err != nil {
+ return nil, httperrors.NewGeneralError(err)
+ }
+ ret := jsonutils.NewDict()
+ ret.Add(jsonutils.NewString(jnlp), "jnlp")
+ return ret, nil
+}
diff --git a/pkg/compute/models/loadbalancernetworks.go b/pkg/compute/models/loadbalancernetworks.go
index aa9f792620..eb3d90c195 100644
--- a/pkg/compute/models/loadbalancernetworks.go
+++ b/pkg/compute/models/loadbalancernetworks.go
@@ -24,6 +24,7 @@ import (
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/sqlchemy"
+ api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -79,9 +80,9 @@ func (ln *SLoadbalancerNetwork) Network() *SNetwork {
type SLoadbalancerNetworkRequestData struct {
Loadbalancer *SLoadbalancer
NetworkId string
- reserved bool // allocate from reserved
- Address string // the address user intends to use
- strategy IPAddlocationDirection // allocate bottom up, top down, randomly
+ reserved bool // allocate from reserved
+ Address string // the address user intends to use
+ strategy api.IPAllocationDirection // allocate bottom up, top down, randomly
}
type SLoadbalancerNetworkDeleteData struct {
diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go
index 1fa2980b53..1b0edfe075 100644
--- a/pkg/compute/models/networks.go
+++ b/pkg/compute/models/networks.go
@@ -43,6 +43,7 @@ import (
"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/billing"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/rand"
"yunion.io/x/onecloud/pkg/util/rbacutils"
@@ -52,16 +53,6 @@ var (
ALL_NETWORK_TYPES = api.ALL_NETWORK_TYPES
)
-type IPAddlocationDirection string
-
-const (
- IPAllocationStepdown IPAddlocationDirection = "stepdown"
- IPAllocationStepup IPAddlocationDirection = "stepup"
- IPAllocationRadnom IPAddlocationDirection = "random"
- IPAllocationNone IPAddlocationDirection = "none"
- IPAllocationDefault = ""
-)
-
type SNetworkManager struct {
db.SSharableVirtualResourceBaseManager
}
@@ -289,7 +280,7 @@ func (self *SNetwork) GetUsedAddresses() map[string]bool {
GuestnetworkManager.Query().SubQuery(),
GroupnetworkManager.Query().SubQuery(),
HostnetworkManager.Query().SubQuery(),
- ReservedipManager.Query().SubQuery(),
+ ReservedipManager.Query().GT("expired_at", time.Now()).SubQuery(),
LoadbalancernetworkManager.Query().SubQuery(),
ElasticipManager.Query().SubQuery(),
NetworkinterfacenetworkManager.Query().SubQuery(),
@@ -339,7 +330,7 @@ func isIpUsed(ipstr string, addrTable map[string]bool, recentUsedAddrTable map[s
}
}
-func (self *SNetwork) getFreeIP(addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir IPAddlocationDirection) (string, error) {
+func (self *SNetwork) getFreeIP(addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir api.IPAllocationDirection) (string, error) {
iprange := self.getIPRange()
// Try candidate first
if len(candidate) > 0 {
@@ -354,10 +345,10 @@ func (self *SNetwork) getFreeIP(addrTable map[string]bool, recentUsedAddrTable m
return candidate, nil
}
}
- if len(self.AllocPolicy) > 0 && IPAddlocationDirection(self.AllocPolicy) != IPAllocationNone {
- allocDir = IPAddlocationDirection(self.AllocPolicy)
+ if len(self.AllocPolicy) > 0 && api.IPAllocationDirection(self.AllocPolicy) != api.IPAllocationNone {
+ allocDir = api.IPAllocationDirection(self.AllocPolicy)
}
- if len(allocDir) == 0 || allocDir == IPAllocationStepdown {
+ if len(allocDir) == 0 || allocDir == api.IPAllocationStepdown {
ip, _ := netutils.NewIPV4Addr(self.GuestIpEnd)
for iprange.Contains(ip) {
if !isIpUsed(ip.String(), addrTable, recentUsedAddrTable) {
@@ -366,7 +357,7 @@ func (self *SNetwork) getFreeIP(addrTable map[string]bool, recentUsedAddrTable m
ip = ip.StepDown()
}
} else {
- if allocDir == IPAllocationRadnom {
+ if allocDir == api.IPAllocationRadnom {
iprange := self.getIPRange()
const MAX_TRIES = 5
for i := 0; i < MAX_TRIES; i += 1 {
@@ -388,21 +379,29 @@ func (self *SNetwork) getFreeIP(addrTable map[string]bool, recentUsedAddrTable m
return "", httperrors.NewInsufficientResourceError("Out of IP address")
}
-func (self *SNetwork) GetFreeIP(ctx context.Context, userCred mcclient.TokenCredential, addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir IPAddlocationDirection, reserved bool) (string, error) {
+func (self *SNetwork) GetFreeIP(ctx context.Context, userCred mcclient.TokenCredential, addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir api.IPAllocationDirection, reserved bool) (string, error) {
+ // if reserved true, first try find IP in reserved IP pool
if reserved {
rip := ReservedipManager.GetReservedIP(self, candidate)
- if rip == nil {
- return "", httperrors.NewInsufficientResourceError("Reserved address %s not found", candidate)
+ if rip != nil {
+ rip.Release(ctx, userCred, self)
+ return candidate, nil
}
- rip.Release(ctx, userCred, self)
- return candidate, nil
- } else {
- cand, err := self.getFreeIP(addrTable, recentUsedAddrTable, candidate, allocDir)
- if err != nil {
- return "", err
- }
- return cand, nil
+ // return "", httperrors.NewInsufficientResourceError("Reserved address %s not found", candidate)
+ // if not find, warning, then fallback to normal procedure
+ log.Warningf("Reserved address %s not found", candidate)
}
+ if addrTable == nil {
+ addrTable = self.GetUsedAddresses()
+ }
+ if recentUsedAddrTable == nil {
+ recentUsedAddrTable = GuestnetworkManager.getRecentlyReleasedIPAddresses(self.Id, self.getAllocTimoutDuration())
+ }
+ cand, err := self.getFreeIP(addrTable, recentUsedAddrTable, candidate, allocDir)
+ if err != nil {
+ return "", err
+ }
+ return cand, nil
}
func (self *SNetwork) GetUsedIfnames() map[string]bool {
@@ -1041,23 +1040,20 @@ func (self *SNetwork) PerformReserveIp(ctx context.Context, userCred mcclient.To
if err != nil {
return nil, httperrors.NewMissingParameterError("ips")
}
+
+ var duration time.Duration
+ durationStr, _ := data.GetString("duration")
+ if len(durationStr) > 0 {
+ bc, err := billing.ParseBillingCycle(durationStr)
+ if err != nil {
+ return nil, httperrors.NewInputParameterError("Duration %s invalid", durationStr)
+ }
+ duration = bc.Duration()
+ }
+
for _, ip := range ips {
ipstr, _ := ip.GetString()
- ipAddr, err := netutils.NewIPV4Addr(ipstr)
- if err != nil {
- return nil, httperrors.NewInputParameterError("not a valid ip address %s: %s", ipstr, err)
- }
- if !self.IsAddressInRange(ipAddr) {
- return nil, httperrors.NewInputParameterError("Address %s not in network", ipstr)
- }
- used, err := self.isAddressUsed(ipstr)
- if err != nil {
- return nil, httperrors.NewInternalServerError("isAddressUsed fail %s", err)
- }
- if used {
- return nil, httperrors.NewConflictError("Address %s has been used", ipstr)
- }
- err = ReservedipManager.ReserveIP(userCred, self, ipstr, notes)
+ err := self.reserveIpWithDuration(ctx, userCred, ipstr, notes, duration)
if err != nil {
return nil, err
}
@@ -1065,6 +1061,28 @@ func (self *SNetwork) PerformReserveIp(ctx context.Context, userCred mcclient.To
return nil, nil
}
+func (self *SNetwork) reserveIpWithDuration(ctx context.Context, userCred mcclient.TokenCredential, ipstr string, notes string, duration time.Duration) error {
+ ipAddr, err := netutils.NewIPV4Addr(ipstr)
+ if err != nil {
+ return httperrors.NewInputParameterError("not a valid ip address %s: %s", ipstr, err)
+ }
+ if !self.IsAddressInRange(ipAddr) {
+ return httperrors.NewInputParameterError("Address %s not in network", ipstr)
+ }
+ used, err := self.isAddressUsed(ipstr)
+ if err != nil {
+ return httperrors.NewInternalServerError("isAddressUsed fail %s", err)
+ }
+ if used {
+ return httperrors.NewConflictError("Address %s has been used", ipstr)
+ }
+ err = ReservedipManager.ReserveIPWithDuration(userCred, self, ipstr, notes, duration)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func (self *SNetwork) AllowPerformReleaseReservedIp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "release-reserved-ip")
}
@@ -1074,7 +1092,7 @@ func (self *SNetwork) PerformReleaseReservedIp(ctx context.Context, userCred mcc
if len(ipstr) == 0 {
return nil, httperrors.NewInputParameterError("Reserved ip to release must be provided")
}
- rip := ReservedipManager.GetReservedIP(self, ipstr)
+ rip := ReservedipManager.getReservedIP(self, ipstr)
if rip == nil {
return nil, httperrors.NewInvalidStatusError("Address %s not reserved", ipstr)
}
@@ -2164,14 +2182,6 @@ func (network *SNetwork) getAllocTimoutDuration() time.Duration {
return time.Duration(tos) * time.Second
}
-func (manager *SNetworkManager) IsValidOnPremiseNetworkIP(ipStr string) bool {
- net, _ := manager.GetOnPremiseNetworkOfIP(ipStr, "", tristate.None)
- if net != nil {
- return true
- }
- return false
-}
-
func (network *SNetwork) GetSchedtags() []SSchedtag {
return GetSchedtags(NetworkschedtagManager, network.Id)
}
diff --git a/pkg/compute/models/reservedips.go b/pkg/compute/models/reservedips.go
index 73533bc256..c1761244d5 100644
--- a/pkg/compute/models/reservedips.go
+++ b/pkg/compute/models/reservedips.go
@@ -17,6 +17,7 @@ package models
import (
"context"
"strconv"
+ "time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
@@ -53,6 +54,8 @@ type SReservedip struct {
IpAddr string `width:"16" charset:"ascii" list:"admin"` // Column(VARCHAR(16, charset='ascii'))
Notes string `width:"512" charset:"utf8" nullable:"true" list:"admin" update:"admin"` // ]Column(VARCHAR(512, charset='utf8'), nullable=True)
+
+ ExpiredAt time.Time `nullable:"true" list:"admin"`
}
func (manager *SReservedipManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
@@ -76,7 +79,15 @@ func (self *SReservedip) AllowDeleteItem(ctx context.Context, userCred mcclient.
}
func (manager *SReservedipManager) ReserveIP(userCred mcclient.TokenCredential, network *SNetwork, ip string, notes string) error {
- rip := SReservedip{NetworkId: network.Id, IpAddr: ip, Notes: notes}
+ return manager.ReserveIPWithDuration(userCred, network, ip, notes, 0)
+}
+
+func (manager *SReservedipManager) ReserveIPWithDuration(userCred mcclient.TokenCredential, network *SNetwork, ip string, notes string, duration time.Duration) error {
+ expiredAt := time.Time{}
+ if duration > 0 {
+ expiredAt = time.Now().UTC().Add(duration)
+ }
+ rip := SReservedip{NetworkId: network.Id, IpAddr: ip, Notes: notes, ExpiredAt: expiredAt}
err := manager.TableSpec().Insert(&rip)
if err != nil {
log.Errorf("ReserveIP fail: %s", err)
@@ -86,11 +97,12 @@ func (manager *SReservedipManager) ReserveIP(userCred mcclient.TokenCredential,
return nil
}
-func (manager *SReservedipManager) GetReservedIP(network *SNetwork, ip string) *SReservedip {
+func (manager *SReservedipManager) getReservedIP(network *SNetwork, ip string) *SReservedip {
rip := SReservedip{}
rip.SetModelManager(manager, &rip)
- err := manager.Query().Equals("network_id", network.Id).Equals("ip_addr", ip).First(&rip)
+ q := manager.Query().Equals("network_id", network.Id).Equals("ip_addr", ip)
+ err := q.First(&rip)
if err != nil {
log.Errorf("GetReservedIP fail: %s", err)
return nil
@@ -98,9 +110,21 @@ func (manager *SReservedipManager) GetReservedIP(network *SNetwork, ip string) *
return &rip
}
+func (manager *SReservedipManager) GetReservedIP(network *SNetwork, ip string) *SReservedip {
+ rip := manager.getReservedIP(network, ip)
+ if rip == nil {
+ return nil
+ }
+ if rip.IsExpired() {
+ return nil
+ }
+ return rip
+}
+
func (manager *SReservedipManager) GetReservedIPs(network *SNetwork) []SReservedip {
rips := make([]SReservedip, 0)
- q := manager.Query().Equals("network_id", network.Id)
+ now := time.Now().UTC()
+ q := manager.Query().Equals("network_id", network.Id).GT("expired_at", now)
err := db.FetchModelObjects(manager, q, &rips)
if err != nil {
log.Errorf("GetReservedIPs fail: %s", err)
@@ -135,6 +159,11 @@ func (self *SReservedip) GetCustomizeColumns(ctx context.Context, userCred mccli
if net != nil {
extra.Add(jsonutils.NewString(net.Name), "network")
}
+ if self.IsExpired() {
+ extra.Add(jsonutils.JSONTrue, "expired")
+ } else {
+ extra.Add(jsonutils.JSONFalse, "expired")
+ }
return extra
}
@@ -144,6 +173,13 @@ func (manager *SReservedipManager) ListItemFilter(ctx context.Context, q *sqlche
log.Errorf("ListItemFilter %s", err)
return nil, err
}
+ isAll := jsonutils.QueryBoolean(query, "all", false)
+ if !isAll {
+ q = q.Filter(sqlchemy.OR(
+ sqlchemy.IsNullOrEmpty(q.Field("expired_at")),
+ sqlchemy.GT(q.Field("expired_at"), time.Now().UTC()),
+ ))
+ }
network, _ := query.GetString("network")
if len(network) > 0 {
netObj, _ := NetworkManager.FetchByIdOrName(userCred, network)
@@ -162,3 +198,10 @@ func (rip *SReservedip) GetId() string {
func (rip *SReservedip) GetName() string {
return rip.GetId()
}
+
+func (rip *SReservedip) IsExpired() bool {
+ if !rip.ExpiredAt.IsZero() && rip.ExpiredAt.Before(time.Now().UTC()) {
+ return true
+ }
+ return false
+}
diff --git a/pkg/compute/models/storagecaches.go b/pkg/compute/models/storagecaches.go
index f609c7c70c..52a88c6bd4 100644
--- a/pkg/compute/models/storagecaches.go
+++ b/pkg/compute/models/storagecaches.go
@@ -156,9 +156,14 @@ func (self *SStoragecache) getHostId() (string, error) {
hosts := make([]SHost, 0)
host := HostManager.Query().SubQuery()
q := host.Query(host.Field("id"))
- err := q.Join(hoststorages, sqlchemy.AND(sqlchemy.Equals(hoststorages.Field("host_id"), host.Field("id")),
- sqlchemy.Equals(host.Field("host_status"), api.HOST_ONLINE),
- sqlchemy.IsTrue(host.Field("enabled")))).
+ err := q.Join(hoststorages, sqlchemy.AND(
+ sqlchemy.Equals(hoststorages.Field("host_id"), host.Field("id")),
+ sqlchemy.OR(
+ sqlchemy.Equals(host.Field("host_status"), api.HOST_ONLINE),
+ sqlchemy.Equals(host.Field("host_type"), api.HOST_TYPE_BAREMETAL),
+ ),
+ sqlchemy.IsTrue(host.Field("enabled")),
+ )).
Join(storages, sqlchemy.AND(sqlchemy.Equals(storages.Field("storagecache_id"), self.Id),
sqlchemy.In(storages.Field("status"), []string{api.STORAGE_ENABLED, api.STORAGE_ONLINE}),
sqlchemy.IsTrue(storages.Field("enabled")))).
@@ -435,6 +440,10 @@ func (self *SStoragecache) ValidateDeleteCondition(ctx context.Context) error {
if self.getCachedImageCount() > 0 {
return httperrors.NewNotEmptyError("storage cache not empty")
}
+ storages := self.getStorages()
+ if len(storages) > 0 {
+ return httperrors.NewNotEmptyError("referered by storages")
+ }
return self.SStandaloneResourceBase.ValidateDeleteCondition(ctx)
}
@@ -620,3 +629,19 @@ func (self *SStoragecache) StartRelinquishLeastUsedCachedImageTask(ctx context.C
}
return self.StartImageUncacheTask(ctx, userCred, cachedImages[leastUsedIdx].GetId(), false, parentTaskId)
}
+
+func (cache *SStoragecache) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
+ err := cache.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data)
+ if err != nil {
+ return err
+ }
+ if len(cache.ExternalId) > 0 {
+ agentObj, err := BaremetalagentManager.FetchById(cache.ExternalId)
+ if err == nil {
+ agentObj.(*SBaremetalagent).setStoragecacheId("")
+ } else if err != sql.ErrNoRows {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/compute/tasks/baremetal_create_task.go b/pkg/compute/tasks/baremetal_create_task.go
new file mode 100644
index 0000000000..9d453c6b8a
--- /dev/null
+++ b/pkg/compute/tasks/baremetal_create_task.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 tasks
+
+import (
+ "context"
+
+ "yunion.io/x/jsonutils"
+
+ "yunion.io/x/onecloud/pkg/cloudcommon/db"
+ "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
+ "yunion.io/x/onecloud/pkg/compute/models"
+)
+
+type BaremetalCreateTask struct {
+ SBaremetalBaseTask
+}
+
+func init() {
+ taskman.RegisterTask(BaremetalCreateTask{})
+}
+
+func (self *BaremetalCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
+ baremetal := obj.(*models.SHost)
+ self.SetStage("OnIpmiProbeComplete", nil)
+ baremetal.StartIpmiProbeTask(ctx, self.UserCred, self.GetTaskId())
+}
+
+func (self *BaremetalCreateTask) OnIpmiProbeComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
+ baremetal := obj.(*models.SHost)
+ ipmiInfo, _ := baremetal.GetIpmiInfo()
+ if !ipmiInfo.Verified {
+ self.SetStageComplete(ctx, nil)
+ return
+ }
+ if jsonutils.QueryBoolean(self.Params, "no_prepare", false) {
+ self.SetStageComplete(ctx, nil)
+ return
+ }
+ if (baremetal.EnablePxeBoot.IsFalse() || !ipmiInfo.PxeBoot) && !ipmiInfo.CdromBoot {
+ self.SetStageComplete(ctx, nil)
+ return
+ }
+ if baremetal.AccessMac == "" && !ipmiInfo.CdromBoot {
+ self.SetStageComplete(ctx, nil)
+ return
+ }
+ self.SetStage("OnPrepareComplete", nil)
+ baremetal.StartPrepareTask(ctx, self.UserCred, "", self.GetTaskId())
+}
+
+func (self *BaremetalCreateTask) OnIpmiProbeCompleteFailed(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
+ self.SetStageFailed(ctx, body.String())
+}
+
+func (self *BaremetalCreateTask) OnPrepareComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
+ self.SetStageComplete(ctx, nil)
+}
+
+func (self *BaremetalCreateTask) OnPrepareCompleteFailed(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
+ self.SetStageFailed(ctx, body.String())
+}
diff --git a/pkg/compute/tasks/baremetal_ipmi_probe_task.go b/pkg/compute/tasks/baremetal_ipmi_probe_task.go
new file mode 100644
index 0000000000..36c28ca0d0
--- /dev/null
+++ b/pkg/compute/tasks/baremetal_ipmi_probe_task.go
@@ -0,0 +1,62 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tasks
+
+import (
+ "context"
+ "fmt"
+
+ "yunion.io/x/jsonutils"
+
+ api "yunion.io/x/onecloud/pkg/apis/compute"
+ "yunion.io/x/onecloud/pkg/cloudcommon/db"
+ "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
+ "yunion.io/x/onecloud/pkg/compute/models"
+)
+
+type BaremetalIpmiProbeTask struct {
+ SBaremetalBaseTask
+}
+
+func init() {
+ taskman.RegisterTask(BaremetalIpmiProbeTask{})
+}
+
+func (self *BaremetalIpmiProbeTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
+ baremetal := obj.(*models.SHost)
+ baremetal.SetStatus(self.UserCred, api.BAREMETAL_PROBING, "")
+ url := fmt.Sprintf("/baremetals/%s/ipmi-probe", baremetal.Id)
+ headers := self.GetTaskRequestHeader()
+ self.SetStage("OnSyncConfigComplete", nil)
+ _, err := baremetal.BaremetalSyncRequest(ctx, "POST", url, headers, self.Params)
+ if err != nil {
+ self.OnFailure(ctx, baremetal, err.Error())
+ }
+}
+
+func (self *BaremetalIpmiProbeTask) OnFailure(ctx context.Context, baremetal *models.SHost, reason string) {
+ baremetal.SetStatus(self.UserCred, api.BAREMETAL_PROBE_FAIL, reason)
+ self.SetStageFailed(ctx, reason)
+}
+
+func (self *BaremetalIpmiProbeTask) OnSyncConfigComplete(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) {
+ // baremetal.ClearSchedDescCache()
+ self.SetStageComplete(ctx, nil)
+}
+
+func (self *BaremetalIpmiProbeTask) OnSyncConfigCompleteFailed(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) {
+ reason, _ := body.GetString("__reason__")
+ self.OnFailure(ctx, baremetal, reason)
+}
diff --git a/pkg/compute/tasks/eip_allocate_task.go b/pkg/compute/tasks/eip_allocate_task.go
index 686788a657..e8aaec8f7d 100644
--- a/pkg/compute/tasks/eip_allocate_task.go
+++ b/pkg/compute/tasks/eip_allocate_task.go
@@ -92,8 +92,7 @@ func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel
lockman.LockObject(ctx, network)
defer lockman.ReleaseObject(ctx, network)
- addrTable := network.GetUsedAddresses()
- ipAddr, err := network.GetFreeIP(ctx, self.UserCred, addrTable, nil, ip, models.IPAllocationNone, false)
+ ipAddr, err := network.GetFreeIP(ctx, self.UserCred, nil, nil, ip, api.IPAllocationNone, false)
if err != nil {
self.onFailed(ctx, eip, err.Error())
return
diff --git a/pkg/hostman/hostinfo/hostinfo.go b/pkg/hostman/hostinfo/hostinfo.go
index d687fc902f..5bb4f41a1a 100644
--- a/pkg/hostman/hostinfo/hostinfo.go
+++ b/pkg/hostman/hostinfo/hostinfo.go
@@ -101,7 +101,7 @@ func (h *SHostInfo) GetHostId() string {
return h.HostId
}
-func (h *SHostInfo) GetZone() string {
+func (h *SHostInfo) GetZoneName() string {
return h.Zone
}
diff --git a/pkg/hostman/hostutils/hostutils.go b/pkg/hostman/hostutils/hostutils.go
index 6de4686f6a..3ab9340f22 100644
--- a/pkg/hostman/hostutils/hostutils.go
+++ b/pkg/hostman/hostutils/hostutils.go
@@ -36,7 +36,7 @@ import (
)
type IHost interface {
- GetZone() string
+ GetZoneName() string
GetHostId() string
GetMediumType() string
GetMasterIp() string
@@ -186,7 +186,15 @@ func DelayTaskWithWorker(
wm.DelayTaskWithWorker(ctx, task, params, worker)
}
-func Init() {
+func InitWorkerManager() {
wm = workmanager.NewWorkManger(TaskFailed, TaskComplete, options.HostOptions.DefaultRequestWorkerCount)
+}
+
+func InitK8sWorkerManager() {
k8sWm = workmanager.NewWorkManger(K8sTaskFailed, K8sTaskComplete, options.HostOptions.DefaultRequestWorkerCount)
}
+
+func Init() {
+ InitWorkerManager()
+ InitK8sWorkerManager()
+}
diff --git a/pkg/hostman/storageman/core.go b/pkg/hostman/storageman/core.go
index 4af0416a8d..f3bf9f042b 100644
--- a/pkg/hostman/storageman/core.go
+++ b/pkg/hostman/storageman/core.go
@@ -37,6 +37,10 @@ import (
const MINIMAL_FREE_SPACE = 128
+type IStorageManager interface {
+ GetZoneName() string
+}
+
type SStorageManager struct {
host hostutils.IHost
@@ -103,8 +107,8 @@ func (s *SStorageManager) Remove(storage IStorage) {
}
}
-func (s *SStorageManager) GetZone() string {
- return s.host.GetZone()
+func (s *SStorageManager) GetZoneName() string {
+ return s.host.GetZoneName()
}
func (s *SStorageManager) GetHostId() string {
diff --git a/pkg/hostman/storageman/disk_base.go b/pkg/hostman/storageman/disk_base.go
index 0143db3211..f95f12e02f 100644
--- a/pkg/hostman/storageman/disk_base.go
+++ b/pkg/hostman/storageman/disk_base.go
@@ -111,8 +111,8 @@ func (d *SBaseDisk) Resize(context.Context, interface{}) (jsonutils.JSONObject,
return nil, fmt.Errorf("Not implemented")
}
-func (d *SBaseDisk) GetZone() string {
- return d.Storage.GetZone()
+func (d *SBaseDisk) GetZoneName() string {
+ return d.Storage.GetZoneName()
}
func (d *SBaseDisk) DeployGuestFs(diskPath string, guestDesc *jsonutils.JSONDict,
diff --git a/pkg/hostman/storageman/disk_local.go b/pkg/hostman/storageman/disk_local.go
index 04c9f6fc46..127e1b14f4 100644
--- a/pkg/hostman/storageman/disk_local.go
+++ b/pkg/hostman/storageman/disk_local.go
@@ -206,7 +206,7 @@ func (d *SLocalDisk) CreateFromTemplate(ctx context.Context, imageId, format str
func (d *SLocalDisk) createFromTemplate(
ctx context.Context, imageId, format string, imageCacheManager IImageCacheManger,
) (jsonutils.JSONObject, error) {
- imageCache := imageCacheManager.AcquireImage(ctx, imageId, d.GetZone(), "", "")
+ imageCache := imageCacheManager.AcquireImage(ctx, imageId, d.GetZoneName(), "", "")
if imageCache != nil {
defer imageCacheManager.ReleaseImage(imageId)
cacheImagePath := imageCache.GetPath()
diff --git a/pkg/hostman/storageman/disk_rbd.go b/pkg/hostman/storageman/disk_rbd.go
index d418f948d1..88b55fe4d4 100644
--- a/pkg/hostman/storageman/disk_rbd.go
+++ b/pkg/hostman/storageman/disk_rbd.go
@@ -178,7 +178,7 @@ func (d *SRBDDisk) createFromTemplate(ctx context.Context, imageId, format strin
if imageCacheManager == nil {
return nil, fmt.Errorf("failed to find image cache manger for storage %s", d.Storage.GetStorageName())
}
- imageCache := imageCacheManager.AcquireImage(ctx, imageId, d.GetZone(), "", "")
+ imageCache := imageCacheManager.AcquireImage(ctx, imageId, d.GetZoneName(), "", "")
if imageCache == nil {
return nil, fmt.Errorf("failed to qcquire image for storage %s", d.Storage.GetStorageName())
}
diff --git a/pkg/hostman/storageman/imagecachemanager_base.go b/pkg/hostman/storageman/imagecachemanager_base.go
index b2c33921cb..3efce15007 100644
--- a/pkg/hostman/storageman/imagecachemanager_base.go
+++ b/pkg/hostman/storageman/imagecachemanager_base.go
@@ -58,7 +58,7 @@ type IImageCacheManger interface {
}
type SBaseImageCacheManager struct {
- storagemanager *SStorageManager
+ storageManager IStorageManager
storagecacaheId string
cachePath string
cachedImages map[string]IImageCache
@@ -76,3 +76,7 @@ func (c *SBaseImageCacheManager) GetId() string {
func (c *SBaseImageCacheManager) SetStoragecacheId(scid string) {
c.storagecacaheId = scid
}
+
+func (c *SBaseImageCacheManager) GetStorageManager() IStorageManager {
+ return c.storageManager
+}
diff --git a/pkg/hostman/storageman/imagecachemanager_local.go b/pkg/hostman/storageman/imagecachemanager_local.go
index 20a1e0435f..4e310e4ae6 100644
--- a/pkg/hostman/storageman/imagecachemanager_local.go
+++ b/pkg/hostman/storageman/imagecachemanager_local.go
@@ -32,12 +32,12 @@ import (
type SLocalImageCacheManager struct {
SBaseImageCacheManager
// limit int
- isTemplate bool
+ // isTemplate bool
}
-func NewLocalImageCacheManager(manager *SStorageManager, cachePath string, storagecacheId string) *SLocalImageCacheManager {
+func NewLocalImageCacheManager(manager IStorageManager, cachePath string, storagecacheId string) *SLocalImageCacheManager {
imageCacheManager := new(SLocalImageCacheManager)
- imageCacheManager.storagemanager = manager
+ imageCacheManager.storageManager = manager
imageCacheManager.storagecacaheId = storagecacheId
imageCacheManager.cachePath = cachePath
// imageCacheManager.limit = limit
@@ -130,7 +130,7 @@ func (c *SLocalImageCacheManager) PrefetchImageCache(ctx context.Context, data i
format, _ := body.GetString("format")
srcUrl, _ := body.GetString("src_url")
- if imgCache := c.AcquireImage(ctx, imageId, storageManager.GetZone(),
+ if imgCache := c.AcquireImage(ctx, imageId, c.GetStorageManager().GetZoneName(),
srcUrl, format); imgCache != nil {
defer imgCache.Release()
diff --git a/pkg/hostman/storageman/imagecachemanager_rbd.go b/pkg/hostman/storageman/imagecachemanager_rbd.go
index 17aaf23fcc..288f2c368c 100644
--- a/pkg/hostman/storageman/imagecachemanager_rbd.go
+++ b/pkg/hostman/storageman/imagecachemanager_rbd.go
@@ -35,10 +35,10 @@ type SRbdImageCacheManager struct {
storage IStorage
}
-func NewRbdImageCacheManager(manager *SStorageManager, cachePath string, storage IStorage, storagecacheId string) *SRbdImageCacheManager {
+func NewRbdImageCacheManager(manager IStorageManager, cachePath string, storage IStorage, storagecacheId string) *SRbdImageCacheManager {
imageCacheManager := new(SRbdImageCacheManager)
- imageCacheManager.storagemanager = manager
+ imageCacheManager.storageManager = manager
imageCacheManager.storagecacaheId = storagecacheId
imageCacheManager.storage = storage
diff --git a/pkg/hostman/storageman/storage_base.go b/pkg/hostman/storageman/storage_base.go
index afa02f7339..9d1534288b 100644
--- a/pkg/hostman/storageman/storage_base.go
+++ b/pkg/hostman/storageman/storage_base.go
@@ -74,7 +74,7 @@ func NewStorage(manager *SStorageManager, mountPoint, storageType string) IStora
type IStorage interface {
GetId() string
GetStorageName() string
- GetZone() string
+ GetZoneName() string
SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject)
SyncStorageInfo() (jsonutils.JSONObject, error)
@@ -169,8 +169,8 @@ func (s *SBaseStorage) SetPath(p string) {
s.Path = p
}
-func (s *SBaseStorage) GetZone() string {
- return s.Manager.GetZone()
+func (s *SBaseStorage) GetZoneName() string {
+ return s.Manager.GetZoneName()
}
func (s *SBaseStorage) GetCapacity() int {
diff --git a/pkg/hostman/storageman/storage_local.go b/pkg/hostman/storageman/storage_local.go
index 19369db099..7862ad0b58 100644
--- a/pkg/hostman/storageman/storage_local.go
+++ b/pkg/hostman/storageman/storage_local.go
@@ -86,7 +86,7 @@ func (s *SLocalStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
content.Set("capacity", jsonutils.NewInt(int64(s.GetAvailSizeMb())))
content.Set("storage_type", jsonutils.NewString(s.StorageType()))
content.Set("medium_type", jsonutils.NewString(s.GetMediumType()))
- content.Set("zone", jsonutils.NewString(s.GetZone()))
+ content.Set("zone", jsonutils.NewString(s.GetZoneName()))
if len(s.Manager.LocalStorageImagecacheManager.GetId()) > 0 {
content.Set("storagecache_id",
jsonutils.NewString(s.Manager.LocalStorageImagecacheManager.GetId()))
@@ -283,7 +283,7 @@ func (s *SLocalStorage) saveToGlance(ctx context.Context, imageId, imagePath str
}
params.Set("image_id", jsonutils.NewString(imageId))
- _, err = modules.Images.Upload(hostutils.GetImageSession(ctx, s.GetZone()),
+ _, err = modules.Images.Upload(hostutils.GetImageSession(ctx, s.GetZoneName()),
params, f, size)
return err
}
@@ -291,7 +291,7 @@ func (s *SLocalStorage) saveToGlance(ctx context.Context, imageId, imagePath str
func (s *SLocalStorage) onSaveToGlanceFailed(ctx context.Context, imageId string) {
params := jsonutils.NewDict()
params.Set("status", jsonutils.NewString("killed"))
- _, err := modules.Images.Update(hostutils.GetImageSession(ctx, s.GetZone()),
+ _, err := modules.Images.Update(hostutils.GetImageSession(ctx, s.GetZoneName()),
imageId, params)
if err != nil {
log.Errorln(err)
diff --git a/pkg/hostman/storageman/storage_nas.go b/pkg/hostman/storageman/storage_nas.go
index 3769f30ae9..45bcb81143 100644
--- a/pkg/hostman/storageman/storage_nas.go
+++ b/pkg/hostman/storageman/storage_nas.go
@@ -86,7 +86,7 @@ func (s *SNasStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
content := jsonutils.NewDict()
content.Set("capacity", jsonutils.NewInt(int64(s.GetAvailSizeMb())))
content.Set("storage_type", jsonutils.NewString(s.ins.StorageType()))
- content.Set("zone", jsonutils.NewString(s.GetZone()))
+ content.Set("zone", jsonutils.NewString(s.GetZoneName()))
log.Infof("Sync storage info %s", s.StorageId)
res, err := modules.Storages.Put(
hostutils.GetComputeSession(context.Background()),
diff --git a/pkg/hostman/storageman/storage_nfs.go b/pkg/hostman/storageman/storage_nfs.go
index a025a4d0ca..f7885bdc9f 100644
--- a/pkg/hostman/storageman/storage_nfs.go
+++ b/pkg/hostman/storageman/storage_nfs.go
@@ -73,7 +73,7 @@ func (s *SNFSStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
content.Set("capacity", jsonutils.NewInt(int64(s.GetAvailSizeMb())))
content.Set("storage_type", jsonutils.NewString(s.StorageType()))
content.Set("status", jsonutils.NewString(api.STORAGE_ONLINE))
- content.Set("zone", jsonutils.NewString(s.GetZone()))
+ content.Set("zone", jsonutils.NewString(s.GetZoneName()))
log.Infof("Sync storage info %s", s.StorageId)
res, err := modules.Storages.Put(
hostutils.GetComputeSession(context.Background()),
diff --git a/pkg/hostman/storageman/storage_rbd.go b/pkg/hostman/storageman/storage_rbd.go
index e5210e98d3..a76deba77a 100644
--- a/pkg/hostman/storageman/storage_rbd.go
+++ b/pkg/hostman/storageman/storage_rbd.go
@@ -560,7 +560,7 @@ func (s *SRbdStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
"name": s.StorageName,
"capacity": capacity,
"status": api.STORAGE_ONLINE,
- "zone": s.GetZone(),
+ "zone": s.GetZoneName(),
}
return modules.Storages.Put(hostutils.GetComputeSession(context.Background()), s.StorageId, jsonutils.Marshal(content))
}
@@ -639,7 +639,7 @@ func (s *SRbdStorage) SaveToGlance(ctx context.Context, params interface{}) (jso
func (s *SRbdStorage) onSaveToGlanceFailed(ctx context.Context, imageId string) {
params := jsonutils.NewDict()
params.Set("status", jsonutils.NewString("killed"))
- _, err := modules.Images.Update(hostutils.GetImageSession(ctx, s.GetZone()),
+ _, err := modules.Images.Update(hostutils.GetImageSession(ctx, s.GetZoneName()),
imageId, params)
if err != nil {
log.Errorln(err)
@@ -695,7 +695,7 @@ func (s *SRbdStorage) saveToGlance(ctx context.Context, imageId, imagePath strin
}
params.Set("image_id", jsonutils.NewString(imageId))
- _, err = modules.Images.Upload(hostutils.GetImageSession(ctx, s.GetZone()),
+ _, err = modules.Images.Upload(hostutils.GetImageSession(ctx, s.GetZoneName()),
params, f, size)
return err
}
diff --git a/pkg/mcclient/modules/mod_hosts.go b/pkg/mcclient/modules/mod_hosts.go
index 127b54c6d1..3ad2d678a6 100644
--- a/pkg/mcclient/modules/mod_hosts.go
+++ b/pkg/mcclient/modules/mod_hosts.go
@@ -184,7 +184,7 @@ var (
func init() {
Hosts = HostManager{NewComputeManager("host", "hosts",
- []string{"ID", "Name", "Access_mac", "Access_ip",
+ []string{"ID", "Name", "Access_mac", "Access_ip", "Ipmi_Ip",
"Manager_URI",
"Status", "enabled", "host_status",
"Guests", "Running_guests",
diff --git a/pkg/mcclient/modules/mod_reservedips.go b/pkg/mcclient/modules/mod_reservedips.go
index 035126fccd..d242ebd618 100644
--- a/pkg/mcclient/modules/mod_reservedips.go
+++ b/pkg/mcclient/modules/mod_reservedips.go
@@ -92,7 +92,7 @@ func (this *ReservedIPManager) DoBatchReleaseReservedIPs(s *mcclient.ClientSessi
func init() {
ReservedIPs = ReservedIPManager{NewComputeManager("reservedip", "reservedips",
[]string{},
- []string{"Network_ID", "Network", "IP_addr", "Notes"})}
+ []string{"Network_ID", "Network", "IP_addr", "Notes", "Expired_At", "Expired"})}
registerCompute(&ReservedIPs)
}
diff --git a/pkg/notify/cache/doc.go b/pkg/notify/cache/doc.go
index 7a00422d98..17645129dc 100644
--- a/pkg/notify/cache/doc.go
+++ b/pkg/notify/cache/doc.go
@@ -1 +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 cache // import "yunion.io/x/onecloud/pkg/notify/cache"
diff --git a/pkg/notify/interface/doc.go b/pkg/notify/interface/doc.go
index b3e40a4498..203113f4d6 100644
--- a/pkg/notify/interface/doc.go
+++ b/pkg/notify/interface/doc.go
@@ -1 +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 _interface // import "yunion.io/x/onecloud/pkg/notify/interface"
diff --git a/pkg/notify/rpc/apis/doc.go b/pkg/notify/rpc/apis/doc.go
index e27ffe4328..a1089f153f 100644
--- a/pkg/notify/rpc/apis/doc.go
+++ b/pkg/notify/rpc/apis/doc.go
@@ -1 +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 apis // import "yunion.io/x/onecloud/pkg/notify/rpc/apis"
diff --git a/pkg/notify/rpc/doc.go b/pkg/notify/rpc/doc.go
index 1e79bce4fb..b60fcc9105 100644
--- a/pkg/notify/rpc/doc.go
+++ b/pkg/notify/rpc/doc.go
@@ -1 +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 rpc // import "yunion.io/x/onecloud/pkg/notify/rpc"
diff --git a/pkg/util/billing/billingcycle.go b/pkg/util/billing/billingcycle.go
index ce9f4bff1d..e51841dfb3 100644
--- a/pkg/util/billing/billingcycle.go
+++ b/pkg/util/billing/billingcycle.go
@@ -27,11 +27,12 @@ import (
type TBillingCycleUnit string
const (
- BillingCycleHour = TBillingCycleUnit("H")
- BillingCycleDay = TBillingCycleUnit("D")
- BillingCycleWeek = TBillingCycleUnit("W")
- BillingCycleMonth = TBillingCycleUnit("M")
- BillingCycleYear = TBillingCycleUnit("Y")
+ BillingCycleMinute = TBillingCycleUnit("I")
+ BillingCycleHour = TBillingCycleUnit("H")
+ BillingCycleDay = TBillingCycleUnit("D")
+ BillingCycleWeek = TBillingCycleUnit("W")
+ BillingCycleMonth = TBillingCycleUnit("M")
+ BillingCycleYear = TBillingCycleUnit("Y")
)
var (
@@ -49,6 +50,8 @@ func ParseBillingCycle(cycleStr string) (SBillingCycle, error) {
return cycle, ErrInvalidBillingCycle
}
switch cycleStr[len(cycleStr)-1:] {
+ case string(BillingCycleMinute), strings.ToLower(string(BillingCycleMinute)):
+ cycle.Unit = BillingCycleMinute
case string(BillingCycleHour), strings.ToLower(string(BillingCycleHour)):
cycle.Unit = BillingCycleHour
case string(BillingCycleDay), strings.ToLower(string(BillingCycleDay)):
@@ -75,11 +78,13 @@ func (cycle *SBillingCycle) String() string {
return fmt.Sprintf("%d%s", cycle.Count, cycle.Unit)
}
-func (cycle *SBillingCycle) EndAt(tm time.Time) time.Time {
+func (cycle SBillingCycle) EndAt(tm time.Time) time.Time {
if tm.IsZero() {
tm = time.Now().UTC()
}
switch cycle.Unit {
+ case BillingCycleMinute:
+ return tm.Add(time.Minute * time.Duration(cycle.Count))
case BillingCycleHour:
return tm.Add(time.Hour * time.Duration(cycle.Count))
case BillingCycleDay:
@@ -95,13 +100,18 @@ func (cycle *SBillingCycle) EndAt(tm time.Time) time.Time {
}
}
+func (cycle SBillingCycle) Duration() time.Duration {
+ now := time.Now().UTC()
+ endAt := cycle.EndAt(now)
+ return endAt.Sub(now)
+}
+
func (cycle *SBillingCycle) GetDays() int {
switch cycle.Unit {
+ case BillingCycleMinute:
+ return cycle.Count / 24 / 60
case BillingCycleHour:
- if cycle.Count%24 == 0 {
- return cycle.Count / 24
- }
- return 0
+ return cycle.Count / 24
case BillingCycleDay:
return cycle.Count
case BillingCycleWeek:
@@ -113,16 +123,12 @@ func (cycle *SBillingCycle) GetDays() int {
func (cycle *SBillingCycle) GetWeeks() int {
switch cycle.Unit {
+ case BillingCycleMinute:
+ return cycle.Count / 7 / 24 / 60
case BillingCycleHour:
- if cycle.Count%(7*24) == 0 {
- return cycle.Count / (7 * 24)
- }
- return 0
+ return cycle.Count / 7 / 24
case BillingCycleDay:
- if cycle.Count%7 == 0 {
- return cycle.Count / 7
- }
- return 0
+ return cycle.Count / 7
case BillingCycleWeek:
return cycle.Count
default:
diff --git a/pkg/util/httputils/fs.go b/pkg/util/httputils/fs.go
new file mode 100644
index 0000000000..c30af94b5b
--- /dev/null
+++ b/pkg/util/httputils/fs.go
@@ -0,0 +1,38 @@
+package httputils
+
+import (
+ "net/http"
+ "strings"
+)
+
+// http filesystem that prevent directory listing
+// https://gist.github.com/hauxe/f2ea1901216177ccf9550a1b8bd59178#file-http_static_correct-go
+
+// FileSystem custom file system handler
+type FileSystem struct {
+ fs http.FileSystem
+}
+
+// Open opens file
+func (fs FileSystem) Open(path string) (http.File, error) {
+ f, err := fs.fs.Open(path)
+ if err != nil {
+ return nil, err
+ }
+
+ s, err := f.Stat()
+ if s.IsDir() {
+ index := strings.TrimSuffix(path, "/") + "/index.html"
+ if _, err := fs.fs.Open(index); err != nil {
+ return nil, err
+ }
+ }
+
+ return f, nil
+}
+
+func Dir(dir string) http.FileSystem {
+ return FileSystem{
+ http.Dir(dir),
+ }
+}
diff --git a/pkg/util/redfish/bmconsole/bmconsole.go b/pkg/util/redfish/bmconsole/bmconsole.go
new file mode 100644
index 0000000000..dbd65da5a0
--- /dev/null
+++ b/pkg/util/redfish/bmconsole/bmconsole.go
@@ -0,0 +1,73 @@
+// 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 bmconsole
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/util/httputils"
+)
+
+type SBMCConsole struct {
+ client *http.Client
+
+ username string
+ password string
+ host string
+
+ isDebug bool
+}
+
+func NewBMCConsole(host, username, password string, isDebug bool) *SBMCConsole {
+ client := httputils.GetDefaultClient()
+ return &SBMCConsole{
+ client: client,
+ host: host,
+ username: username,
+ password: password,
+ isDebug: isDebug,
+ }
+}
+
+func setCookieHeader(hdr http.Header, cookies map[string]string) {
+ cookieParts := make([]string, 0)
+ for k, v := range cookies {
+ cookieParts = append(cookieParts, k+"="+v)
+ }
+ if len(cookieParts) > 0 {
+ hdr.Set("Cookie", strings.Join(cookieParts, "; "))
+ }
+}
+
+func (r *SBMCConsole) RawRequest(ctx context.Context, method httputils.THttpMethod, path string, header http.Header, body []byte) (http.Header, []byte, error) {
+ urlStr := httputils.JoinPath(fmt.Sprintf("https://%s", r.host), path)
+ if header == nil {
+ header = http.Header{}
+ }
+ header.Set("Connection", "Close")
+ header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:69.0) Gecko/20100101 Firefox/69.0")
+ resp, err := httputils.Request(r.client, ctx, method, urlStr, header, bytes.NewReader(body), r.isDebug)
+ hdr, rspBody, err := httputils.ParseResponse(resp, err, r.isDebug)
+ if err != nil {
+ return nil, nil, errors.Wrap(err, "httputils.Request")
+ }
+ return hdr, rspBody, nil
+}
diff --git a/pkg/util/redfish/bmconsole/doc.go b/pkg/util/redfish/bmconsole/doc.go
new file mode 100644
index 0000000000..0fa8ac108a
--- /dev/null
+++ b/pkg/util/redfish/bmconsole/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 bmconsole // import "yunion.io/x/onecloud/pkg/util/redfish/bmconsole"
diff --git a/pkg/util/redfish/bmconsole/idrac.go b/pkg/util/redfish/bmconsole/idrac.go
new file mode 100644
index 0000000000..d95f0e171c
--- /dev/null
+++ b/pkg/util/redfish/bmconsole/idrac.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 bmconsole
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+
+ "yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/util/httputils"
+)
+
+func (r *SBMCConsole) GetIdracConsoleJNLP(ctx context.Context, sku, model string) (string, error) {
+ loginData := strings.Join([]string{
+ "user=" + url.QueryEscape(r.username),
+ "password=" + url.QueryEscape(r.password),
+ }, "&")
+
+ // cookie:
+ // -http-session-=::http.session::0103fd02ceac2d642361b6fdcd4a5994;
+ // sysidledicon=ledIcon%20grayLed;
+ // tokenvalue=478be97abdaeb4d454c0418fcca9094d
+
+ cookies := make(map[string]string)
+ cookies["-http-session-"] = ""
+
+ // first do html login
+ postHdr := http.Header{}
+ postHdr.Set("Content-Type", "application/x-www-form-urlencoded")
+ setCookieHeader(postHdr, cookies)
+ hdr, loginResp, err := r.RawRequest(ctx, httputils.POST, "/data/login", postHdr, []byte(loginData))
+ if err != nil {
+ return "", errors.Wrap(err, "r.FormPost Login")
+ }
+ for _, cookieHdr := range hdr["Set-Cookie"] {
+ parts := strings.Split(cookieHdr, ";")
+ if len(parts) > 0 {
+ pparts := strings.Split(parts[0], "=")
+ if len(pparts) > 1 {
+ cookies[pparts[0]] = pparts[1]
+ }
+ }
+ }
+ forwardUrlPattern := regexp.MustCompile(`(.*)`)
+ matched := forwardUrlPattern.FindAllStringSubmatch(string(loginResp), -1)
+ indexUrlStr := ""
+ if len(matched) > 0 && len(matched[0]) > 1 {
+ indexUrlStr = matched[0][1]
+ }
+ if len(indexUrlStr) == 0 {
+ return "", errors.Wrapf(httperrors.ErrBadRequest, "no valid forwardUrl")
+ }
+
+ tokenPattern := regexp.MustCompile(`ST1=(\w+),ST2=`)
+ matched = tokenPattern.FindAllStringSubmatch(indexUrlStr, -1)
+ log.Debugf("%s", matched)
+ token := ""
+ if len(matched) > 0 && len(matched[0]) > 1 {
+ token = matched[0][1]
+ }
+ cookies["tokenvalue"] = token
+
+ getHdr := http.Header{}
+ setCookieHeader(getHdr, cookies)
+
+ sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, User: %s", sku, model, r.username))
+ path := fmt.Sprintf("viewer.jnlp(%s@0@%s@%d@ST1=%s)", r.host, sysStr, time.Now().UnixNano()/1000000, token)
+
+ _, rspBody, err := r.RawRequest(ctx, httputils.GET, path, getHdr, nil)
+ if err != nil {
+ return "", errors.Wrapf(err, "r.RawGet %s", path)
+ }
+ return string(rspBody), nil
+}
diff --git a/pkg/util/redfish/bmconsole/ilo.go b/pkg/util/redfish/bmconsole/ilo.go
new file mode 100644
index 0000000000..550407ccf5
--- /dev/null
+++ b/pkg/util/redfish/bmconsole/ilo.go
@@ -0,0 +1,86 @@
+// 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 bmconsole
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "yunion.io/x/jsonutils"
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/util/httputils"
+)
+
+func (r *SBMCConsole) GetIloConsoleJNLP(ctx context.Context) (string, error) {
+ loginData := jsonutils.NewDict()
+ loginData.Add(jsonutils.NewString("login"), "method")
+ loginData.Add(jsonutils.NewString(r.username), "user_login")
+ loginData.Add(jsonutils.NewString(r.password), "password")
+
+ postHdr := http.Header{}
+ postHdr.Set("Content-Type", "application/json")
+ _, loginRespBytes, err := r.RawRequest(ctx, httputils.POST, "/json/login_session", postHdr, []byte(loginData.String()))
+ if err != nil {
+ return "", errors.Wrap(err, "r.FormPost Login")
+ }
+
+ loginRespJson, err := jsonutils.Parse(loginRespBytes)
+ if err != nil {
+ return "", errors.Wrap(err, "jsonutils.Parse loginRespBytes")
+ }
+
+ sessionKey, err := loginRespJson.GetString("session_key")
+ if err != nil {
+ return "", errors.Wrap(err, "Get session_key")
+ }
+
+ endpoint := fmt.Sprintf("https://%s/", r.host)
+
+ cookies := make(map[string]string)
+ cookies["sessionKey"] = sessionKey
+ cookies["sessionLang"] = "en"
+ cookies["sessionUrl"] = endpoint
+
+ getHdr := http.Header{}
+ setCookieHeader(getHdr, cookies)
+ _, tempBytes, err := r.RawRequest(ctx, httputils.GET, "/html/jnlp_template.html", getHdr, nil)
+ if err != nil {
+ return "", errors.Wrap(err, "request template")
+ }
+
+ startToken := []byte("")
+ pos := bytes.Index(tempBytes, startToken)
+ if pos < 0 {
+ return "", errors.Wrapf(err, "invalid template content %s: no start token", tempBytes)
+ }
+ tempBytes = tempBytes[pos+len(startToken):]
+ pos = bytes.Index(tempBytes, endToken)
+ if pos < 0 {
+ return "", errors.Wrapf(err, "invalid template content %s: no end token", tempBytes)
+ }
+ template := string(tempBytes[:pos])
+
+ // replace variables
+ template = strings.ReplaceAll(template, "<%= this.baseUrl %>", endpoint)
+ template = strings.ReplaceAll(template, "<%= this.sessionKey %>", sessionKey)
+ template = strings.ReplaceAll(template, "<%= this.langId %>", "en")
+
+ return template, nil
+}
diff --git a/pkg/util/redfish/doc.go b/pkg/util/redfish/doc.go
new file mode 100644
index 0000000000..1018e2295d
--- /dev/null
+++ b/pkg/util/redfish/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 redfish // import "yunion.io/x/onecloud/pkg/util/redfish"
diff --git a/pkg/util/redfish/driver.go b/pkg/util/redfish/driver.go
index 98ce185291..8c3598b1b4 100644
--- a/pkg/util/redfish/driver.go
+++ b/pkg/util/redfish/driver.go
@@ -1,3 +1,17 @@
+// 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 redfish
import (
@@ -5,6 +19,8 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
+
+ "yunion.io/x/onecloud/pkg/cloudcommon/types"
)
type IRedfishDriverFactory interface {
@@ -32,12 +48,14 @@ type IRedfishDriver interface {
GetResourceCount(ctx context.Context, resname ...string) (int, error)
GetVirtualCdromInfo(ctx context.Context) (string, SCdromInfo, error)
- MountVirtualCdrom(ctx context.Context, path string, cdromUrl string) error
+ MountVirtualCdrom(ctx context.Context, path string, cdromUrl string, boot bool) error
UmountVirtualCdrom(ctx context.Context, path string) error
+ GetLanConfigs(ctx context.Context) ([]types.SIPMILanConfig, error)
+
GetSystemInfo(ctx context.Context) (string, SSystemInfo, error)
SetNextBootDev(ctx context.Context, dev string) error
- SetNextBootVirtualCdrom(ctx context.Context) error
+ // SetNextBootVirtualCdrom(ctx context.Context) error
Reset(ctx context.Context, action string) error
@@ -86,6 +104,9 @@ func NewRedfishDriver(ctx context.Context, endpoint string, username, password s
return drv
}
}
+ if defaultFactory == nil {
+ return nil
+ }
drv := defaultFactory.NewApi(endpoint, username, password, debug)
err := drv.Probe(ctx)
if err == nil {
diff --git a/pkg/util/redfish/generic/doc.go b/pkg/util/redfish/generic/doc.go
new file mode 100644
index 0000000000..13f4adaf16
--- /dev/null
+++ b/pkg/util/redfish/generic/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 generic // import "yunion.io/x/onecloud/pkg/util/redfish/generic"
diff --git a/pkg/util/redfish/generic/generic.go b/pkg/util/redfish/generic/generic.go
index 2da0be3ea5..064d3bbdbf 100644
--- a/pkg/util/redfish/generic/generic.go
+++ b/pkg/util/redfish/generic/generic.go
@@ -1,3 +1,17 @@
+// 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 generic
import (
diff --git a/pkg/util/redfish/hprest/doc.go b/pkg/util/redfish/hprest/doc.go
new file mode 100644
index 0000000000..84957272ff
--- /dev/null
+++ b/pkg/util/redfish/hprest/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 hprest // import "yunion.io/x/onecloud/pkg/util/redfish/hprest"
diff --git a/pkg/util/redfish/hprest/hprest.go b/pkg/util/redfish/hprest/hprest.go
index 3fbdd06991..70902dc449 100644
--- a/pkg/util/redfish/hprest/hprest.go
+++ b/pkg/util/redfish/hprest/hprest.go
@@ -1,3 +1,17 @@
+// 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 hprest
import (
@@ -73,6 +87,10 @@ func (r *SHpRestApi) MemberKey() string {
return memberKey
}
+func (r *SHpRestApi) LogItemsKey() string {
+ return "Items"
+}
+
func (r *SHpRestApi) Probe(ctx context.Context) error {
err := r.SBaseRedfishClient.Probe(ctx)
if err != nil {
@@ -167,7 +185,7 @@ func (r *SHpRestApi) Reset(ctx context.Context, action string) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(action), "ResetType")
params.Add(jsonutils.NewString("Reset"), "Action")
- resp, err := r.Post(ctx, path, params)
+ _, resp, err := r.Post(ctx, path, params)
if err != nil {
return errors.Wrap(err, "Action.Reset")
}
@@ -184,7 +202,7 @@ func (r *SHpRestApi) BmcReset(ctx context.Context) error {
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString("Reset"), "Action")
- resp, err := r.Post(ctx, path, params)
+ _, resp, err := r.Post(ctx, path, params)
if err != nil {
return errors.Wrap(err, "Actions/Manager.Reset")
}
@@ -244,7 +262,7 @@ func (r *SHpRestApi) clearLogs(ctx context.Context, subsys string) error {
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString("ClearLog"), "Action")
- resp, err := r.Post(ctx, path, params)
+ _, resp, err := r.Post(ctx, path, params)
if err != nil {
return errors.Wrap(err, "r.Post")
}
diff --git a/pkg/util/redfish/idrac/doc.go b/pkg/util/redfish/idrac/doc.go
new file mode 100644
index 0000000000..fcea562f0a
--- /dev/null
+++ b/pkg/util/redfish/idrac/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 idrac // import "yunion.io/x/onecloud/pkg/util/redfish/idrac"
diff --git a/pkg/util/redfish/idrac/idrac.go b/pkg/util/redfish/idrac/idrac.go
index db62b1af2c..519aa51231 100644
--- a/pkg/util/redfish/idrac/idrac.go
+++ b/pkg/util/redfish/idrac/idrac.go
@@ -1,11 +1,22 @@
+// 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 idrac
import (
"context"
"fmt"
- "net/http"
- "net/url"
- "regexp"
"strings"
"time"
@@ -16,6 +27,7 @@ import (
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/redfish"
+ "yunion.io/x/onecloud/pkg/util/redfish/bmconsole"
"yunion.io/x/onecloud/pkg/util/redfish/generic"
)
@@ -73,7 +85,7 @@ func (r *SIDracRefishApi) GetVirtualCdromInfo(ctx context.Context) (string, redf
return path, cdInfo, nil
}
-func (r *SIDracRefishApi) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string) error {
+func (r *SIDracRefishApi) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string, boot bool) error {
info := jsonutils.NewDict()
info.Set("Image", jsonutils.NewString(cdromUrl))
@@ -83,6 +95,12 @@ func (r *SIDracRefishApi) MountVirtualCdrom(ctx context.Context, path string, cd
return errors.Wrap(err, "r.Post")
}
// log.Debugf("%s", resp.PrettyString())
+ if boot {
+ err = r.SetNextBootVirtualCdrom(ctx)
+ if err != nil {
+ return errors.Wrap(err, "r.SetNextBootVirtualCdrom")
+ }
+ }
return nil
}
@@ -312,68 +330,6 @@ func (r *SIDracRefishApi) doImportConfig(ctx context.Context, conf iDRACConfig)
}
func (r *SIDracRefishApi) GetConsoleJNLP(ctx context.Context) (string, error) {
- loginData := strings.Join([]string{
- "user=" + url.QueryEscape(r.GetUsername()),
- "password=" + url.QueryEscape(r.GetPassword()),
- }, "&")
-
- // cookie:
- // -http-session-=::http.session::0103fd02ceac2d642361b6fdcd4a5994;
- // sysidledicon=ledIcon%20grayLed;
- // tokenvalue=478be97abdaeb4d454c0418fcca9094d
-
- cookies := make(map[string]string)
- cookies["-http-session-"] = ""
-
- // first do html login
- postHdr := http.Header{}
- postHdr.Set("Content-Type", "application/x-www-form-urlencoded")
- redfish.SetCookieHeader(postHdr, cookies)
- hdr, loginResp, err := r.RawRequest(ctx, httputils.POST, "/data/login", postHdr, []byte(loginData))
- if err != nil {
- return "", errors.Wrap(err, "r.FormPost Login")
- }
- for _, cookieHdr := range hdr["Set-Cookie"] {
- parts := strings.Split(cookieHdr, ";")
- if len(parts) > 0 {
- pparts := strings.Split(parts[0], "=")
- if len(pparts) > 1 {
- cookies[pparts[0]] = pparts[1]
- }
- }
- }
- forwardUrlPattern := regexp.MustCompile(`(.*)`)
- matched := forwardUrlPattern.FindAllStringSubmatch(string(loginResp), -1)
- indexUrlStr := ""
- if len(matched) > 0 && len(matched[0]) > 1 {
- indexUrlStr = matched[0][1]
- }
- if len(indexUrlStr) == 0 {
- return "", errors.Wrapf(httperrors.ErrBadRequest, "no valid forwardUrl")
- }
-
- tokenPattern := regexp.MustCompile(`ST1=(\w+),ST2=`)
- matched = tokenPattern.FindAllStringSubmatch(indexUrlStr, -1)
- log.Debugf("%s", matched)
- token := ""
- if len(matched) > 0 && len(matched[0]) > 1 {
- token = matched[0][1]
- }
- cookies["tokenvalue"] = token
-
- getHdr := http.Header{}
- redfish.SetCookieHeader(getHdr, cookies)
-
- _, sysInfo, err := r.GetSystemInfo(ctx)
- if err != nil {
- return "", errors.Wrap(err, "r.GetSystemInfo")
- }
- sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, User: %s", sysInfo.SKU, sysInfo.Model, r.GetUsername()))
- path := fmt.Sprintf("viewer.jnlp(%s@0@%s@%d@ST1=%s)", r.GetHost(), sysStr, time.Now().UnixNano()/1000000, token)
-
- _, rspBody, err := r.RawRequest(ctx, httputils.GET, path, getHdr, nil)
- if err != nil {
- return "", errors.Wrapf(err, "r.RawGet %s", path)
- }
- return string(rspBody), nil
+ bmc := bmconsole.NewBMCConsole(r.GetHost(), r.GetUsername(), r.GetPassword(), r.IsDebug)
+ return bmc.GetIdracConsoleJNLP(ctx, "", "")
}
diff --git a/pkg/util/redfish/ilo/doc.go b/pkg/util/redfish/ilo/doc.go
new file mode 100644
index 0000000000..516ba63082
--- /dev/null
+++ b/pkg/util/redfish/ilo/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 ilo // import "yunion.io/x/onecloud/pkg/util/redfish/ilo"
diff --git a/pkg/util/redfish/ilo/ilo.go b/pkg/util/redfish/ilo/ilo.go
index c55dc5ae4c..58b1af4230 100644
--- a/pkg/util/redfish/ilo/ilo.go
+++ b/pkg/util/redfish/ilo/ilo.go
@@ -1,9 +1,21 @@
+// 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 ilo
import (
- "bytes"
"context"
- "net/http"
"strings"
"yunion.io/x/jsonutils"
@@ -12,6 +24,7 @@ import (
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/redfish"
+ "yunion.io/x/onecloud/pkg/util/redfish/bmconsole"
"yunion.io/x/onecloud/pkg/util/redfish/generic"
)
@@ -179,6 +192,9 @@ func (r *SILORefishApi) SetNTPConf(ctx context.Context, conf redfish.SNTPConf) e
if err != nil {
return errors.Wrap(err, "r.GetResource Managers 0")
}
+ if len(conf.NTPServers) > 2 {
+ conf.NTPServers = conf.NTPServers[:2]
+ }
dateUrl := httputils.JoinPath(path, "DateTime")
params := jsonutils.NewDict()
params.Add(jsonutils.NewStringArray(conf.NTPServers), "StaticNTPServers")
@@ -194,62 +210,37 @@ func (r *SILORefishApi) SetNTPConf(ctx context.Context, conf redfish.SNTPConf) e
}
func (r *SILORefishApi) GetConsoleJNLP(ctx context.Context) (string, error) {
- loginData := jsonutils.NewDict()
- loginData.Add(jsonutils.NewString("login"), "method")
- loginData.Add(jsonutils.NewString(r.GetUsername()), "user_login")
- loginData.Add(jsonutils.NewString(r.GetPassword()), "password")
-
- postHdr := http.Header{}
- postHdr.Set("Content-Type", "application/json")
- _, loginRespBytes, err := r.RawRequest(ctx, httputils.POST, "/json/login_session", postHdr, []byte(loginData.String()))
- if err != nil {
- return "", errors.Wrap(err, "r.FormPost Login")
- }
-
- loginRespJson, err := jsonutils.Parse(loginRespBytes)
- if err != nil {
- return "", errors.Wrap(err, "jsonutils.Parse loginRespBytes")
- }
-
- sessionKey, err := loginRespJson.GetString("session_key")
- if err != nil {
- return "", errors.Wrap(err, "Get session_key")
- }
-
- endpoint := r.GetEndpoint()
- if !strings.HasSuffix(endpoint, "/") {
- endpoint += "/"
- }
-
- cookies := make(map[string]string)
- cookies["sessionKey"] = sessionKey
- cookies["sessionLang"] = "en"
- cookies["sessionUrl"] = endpoint
-
- getHdr := http.Header{}
- redfish.SetCookieHeader(getHdr, cookies)
- _, tempBytes, err := r.RawRequest(ctx, httputils.GET, "/html/jnlp_template.html", getHdr, nil)
- if err != nil {
- return "", errors.Wrap(err, "request template")
- }
-
- startToken := []byte("")
- pos := bytes.Index(tempBytes, startToken)
- if pos < 0 {
- return "", errors.Wrapf(err, "invalid template content %s: no start token", tempBytes)
- }
- tempBytes = tempBytes[pos+len(startToken):]
- pos = bytes.Index(tempBytes, endToken)
- if pos < 0 {
- return "", errors.Wrapf(err, "invalid template content %s: no end token", tempBytes)
- }
- template := string(tempBytes[:pos])
-
- // replace variables
- template = strings.ReplaceAll(template, "<%= this.baseUrl %>", endpoint)
- template = strings.ReplaceAll(template, "<%= this.sessionKey %>", sessionKey)
- template = strings.ReplaceAll(template, "<%= this.langId %>", "en")
-
- return template, nil
+ bmc := bmconsole.NewBMCConsole(r.GetHost(), r.GetUsername(), r.GetPassword(), r.IsDebug)
+ return bmc.GetIloConsoleJNLP(ctx)
+}
+
+func (r *SILORefishApi) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string, boot bool) error {
+ info := jsonutils.NewDict()
+ info.Set("Image", jsonutils.NewString(cdromUrl))
+ if boot {
+ cdInfo, err := r.Get(ctx, path)
+ if err != nil {
+ return errors.Wrapf(err, "Get %s", path)
+ }
+ var oemKey string
+ _, err = cdInfo.Bool("Oem", "Hp", "BootOnNextServerReset")
+ if err != nil {
+ _, err = cdInfo.Bool("Oem", "Hpe", "BootOnNextServerReset")
+ if err != nil {
+ return errors.Wrap(err, "no BootOnNextServerReset found???")
+ } else {
+ oemKey = "Hpe"
+ }
+ } else {
+ oemKey = "Hp"
+ }
+ info.Add(jsonutils.JSONTrue, "Oem", oemKey, "BootOnNextServerReset")
+ }
+
+ resp, err := r.Patch(ctx, path, info)
+ if err != nil {
+ return errors.Wrap(err, "r.Patch")
+ }
+ log.Debugf("%s", resp.PrettyString())
+ return nil
}
diff --git a/pkg/util/redfish/loader/doc.go b/pkg/util/redfish/loader/doc.go
new file mode 100644
index 0000000000..c7b8e68566
--- /dev/null
+++ b/pkg/util/redfish/loader/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 loader // import "yunion.io/x/onecloud/pkg/util/redfish/loader"
diff --git a/pkg/util/redfish/loader/loader.go b/pkg/util/redfish/loader/loader.go
index 3c8788c7be..478e3da975 100644
--- a/pkg/util/redfish/loader/loader.go
+++ b/pkg/util/redfish/loader/loader.go
@@ -1,3 +1,17 @@
+// 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 loader
import (
diff --git a/pkg/util/redfish/redfish.go b/pkg/util/redfish/redfish.go
index 1d8801ac5b..3a5a745dfb 100644
--- a/pkg/util/redfish/redfish.go
+++ b/pkg/util/redfish/redfish.go
@@ -1,9 +1,23 @@
+// 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 redfish
import (
- "bytes"
"context"
"encoding/base64"
+ "net"
"net/http"
"net/url"
"strconv"
@@ -16,6 +30,7 @@ import (
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/cloudcommon/object"
+ "yunion.io/x/onecloud/pkg/cloudcommon/types"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/httputils"
)
@@ -98,7 +113,7 @@ func (r *SBaseRedfishClient) request(ctx context.Context, method httputils.THttp
return hdr, resp, nil
}
-func SetCookieHeader(hdr http.Header, cookies map[string]string) {
+/*func SetCookieHeader(hdr http.Header, cookies map[string]string) {
cookieParts := make([]string, 0)
for k, v := range cookies {
cookieParts = append(cookieParts, k+"="+v)
@@ -121,7 +136,7 @@ func (r *SBaseRedfishClient) RawRequest(ctx context.Context, method httputils.TH
return nil, nil, errors.Wrap(err, "httputils.Request")
}
return hdr, rspBody, nil
-}
+}*/
func (r *SBaseRedfishClient) Get(ctx context.Context, path string) (jsonutils.JSONObject, error) {
_, resp, err := r.request(ctx, httputils.GET, path, nil, nil)
@@ -322,7 +337,7 @@ func (r *SBaseRedfishClient) GetVirtualCdromInfo(ctx context.Context) (string, S
return path, cdInfo, nil
}
-func (r *SBaseRedfishClient) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string) error {
+func (r *SBaseRedfishClient) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string, boot bool) error {
info := jsonutils.NewDict()
info.Set("Image", jsonutils.NewString(cdromUrl))
@@ -366,6 +381,12 @@ func (r *SBaseRedfishClient) GetSystemInfo(ctx context.Context) (string, SSystem
sysInfo.Model = strings.TrimSpace(sysInfo.Model)
sysInfo.Manufacturer = strings.TrimSpace(sysInfo.Manufacturer)
+ if strings.EqualFold(sysInfo.PowerState, types.POWER_STATUS_ON) {
+ sysInfo.PowerState = types.POWER_STATUS_ON
+ } else {
+ sysInfo.PowerState = types.POWER_STATUS_OFF
+ }
+
memGBStr, _ := resp.GetString("MemorySummary", "TotalSystemMemoryGiB")
memGB, _ := strconv.ParseInt(memGBStr, 10, 64)
if memGB > 0 {
@@ -413,9 +434,16 @@ func (r *SBaseRedfishClient) GetSystemInfo(ctx context.Context) (string, SSystem
if err != nil {
return path, sysInfo, errors.Wrapf(err, "Get EthernetInterface[%d] error", i)
}
- macAddr, _ := nicInfo.GetString("MacAddress")
- if len(macAddr) == 0 {
- macAddr, _ = nicInfo.GetString("PermanentMACAddress")
+ var macAddr string
+ for _, key := range []string{
+ "MacAddress",
+ "MACAddress",
+ "PermanentMACAddress",
+ } {
+ macAddr, _ = nicInfo.GetString(key)
+ if len(macAddr) > 0 {
+ break
+ }
}
sysInfo.EthernetNICs[i] = netutils.FormatMacAddr(macAddr)
}
@@ -685,3 +713,65 @@ func (r *SBaseRedfishClient) SetNTPConf(ctx context.Context, conf SNTPConf) erro
func (r *SBaseRedfishClient) GetConsoleJNLP(ctx context.Context) (string, error) {
return "", httperrors.ErrNotImplemented
}
+
+func (r *SBaseRedfishClient) GetLanConfigs(ctx context.Context) ([]types.SIPMILanConfig, error) {
+ _, ethIfsJson, err := r.GetResource(ctx, "Managers", "0", "EthernetInterfaces")
+ if err != nil {
+ return nil, errors.Wrap(err, "GetResource Managers 0 EthernetInterfaces")
+ }
+ ethIfs, err := ethIfsJson.GetArray(r.IRedfishDriver().MemberKey())
+ if err != nil {
+ return nil, errors.Wrap(err, "GetArray")
+ }
+ ret := make([]types.SIPMILanConfig, 0)
+ for i := range ethIfs {
+ ethLink, _ := ethIfs[i].GetString(r.IRedfishDriver().LinkKey())
+ if len(ethLink) == 0 {
+ continue
+ }
+ ethJson, err := r.Get(ctx, ethLink)
+ if err != nil {
+ continue
+ }
+ v4Addrs, err := ethJson.GetArray("IPv4Addresses")
+ if err != nil {
+ continue
+ }
+ if len(v4Addrs) == 0 {
+ continue
+ }
+ for i := range v4Addrs {
+ addr, err := v4Addrs[i].GetString("Address")
+ if err != nil {
+ continue
+ }
+ if len(addr) > 0 && addr != "0.0.0.0" {
+ // find a config
+ conf := types.SIPMILanConfig{}
+ conf.IPAddr = addr
+ mask, _ := v4Addrs[i].GetString("SubnetMask")
+ conf.Netmask = mask
+ gw, _ := v4Addrs[i].GetString("Gateway")
+ conf.Gateway = gw
+ src, _ := v4Addrs[i].GetString("AddressOrigin")
+ if len(src) == 0 || src == "null" {
+ src = "static"
+ }
+ conf.IPSrc = strings.ToLower(src)
+ mac, _ := ethJson.GetString("MACAddress")
+ conf.Mac, _ = net.ParseMAC(mac)
+ var vlanId int64
+ if ethJson.Contains("VLAN") {
+ vlanId, _ = ethJson.Int("VLAN", "VLANId")
+ } else {
+ vlanId, _ = ethJson.Int("VLANId")
+ }
+ speed, _ := ethJson.Int("SpeedMbps")
+ conf.SpeedMbps = int(speed)
+ conf.VlanId = int(vlanId)
+ ret = append(ret, conf)
+ }
+ }
+ }
+ return ret, nil
+}
diff --git a/pkg/util/redfish/resource.go b/pkg/util/redfish/resource.go
index 278bd7928c..10b34ec809 100644
--- a/pkg/util/redfish/resource.go
+++ b/pkg/util/redfish/resource.go
@@ -1,3 +1,17 @@
+// 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 redfish
import "time"
diff --git a/pkg/util/redfish/utils.go b/pkg/util/redfish/utils.go
new file mode 100644
index 0000000000..ba832b049f
--- /dev/null
+++ b/pkg/util/redfish/utils.go
@@ -0,0 +1,55 @@
+// 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 redfish
+
+import (
+ "context"
+
+ "yunion.io/x/pkg/errors"
+)
+
+func MountVirtualCdrom(ctx context.Context, api IRedfishDriver, cdromUrl string, boot bool) error {
+ path, cdInfo, err := api.GetVirtualCdromInfo(ctx)
+ if err != nil {
+ return errors.Wrap(err, "api.GetVirtualCdromInfo")
+ }
+ if cdInfo.Image == cdromUrl {
+ return nil
+ }
+ if !cdInfo.SupportAction {
+ return errors.Error("action not supported")
+ }
+ if cdInfo.Image != "" {
+ err = api.UmountVirtualCdrom(ctx, path)
+ if err != nil {
+ return errors.Wrap(err, "api.UmountVirtualCdrom")
+ }
+ }
+ return api.MountVirtualCdrom(ctx, path, cdromUrl, boot)
+}
+
+func UmountVirtualCdrom(ctx context.Context, api IRedfishDriver) error {
+ path, cdInfo, err := api.GetVirtualCdromInfo(ctx)
+ if err != nil {
+ return errors.Wrap(err, "api.GetVirtualCdromInfo")
+ }
+ if cdInfo.Image == "" {
+ return nil
+ }
+ if !cdInfo.SupportAction {
+ return errors.Error("action not supported")
+ }
+ return api.UmountVirtualCdrom(ctx, path)
+}