From 7e02839d67e36eb7c203fe71a227680b147a8077 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sun, 6 Oct 2019 00:11:41 +0800 Subject: [PATCH] add doc and copyright --- build/baremetal-agent/vars | 7 +- cmd/climc/shell/baremetalagents.go | 9 + cmd/climc/shell/hosts.go | 58 ++- cmd/climc/shell/reservedips.go | 14 +- cmd/climc/shell/servers.go | 22 + cmd/redfishcli/shell/chassis.go | 14 + cmd/redfishcli/shell/manager.go | 52 ++- cmd/redfishcli/shell/shell.go | 14 + cmd/redfishcli/shell/system.go | 21 +- pkg/apis/compute/host_const.go | 8 + pkg/apis/compute/network_const.go | 10 + pkg/baremetal/agent.go | 34 +- pkg/baremetal/handler/handlers.go | 8 +- pkg/baremetal/handler/middleware.go | 17 +- pkg/baremetal/manager.go | 412 ++++++++++++++++-- pkg/baremetal/nic.go | 11 +- pkg/baremetal/options/options.go | 6 +- pkg/baremetal/pxe/dhcp.go | 13 +- pkg/baremetal/service/service.go | 43 +- pkg/baremetal/tasks/base.go | 31 +- pkg/baremetal/tasks/baseprepare.go | 153 ++++--- pkg/baremetal/tasks/bm_register.go | 4 +- pkg/baremetal/tasks/interface.go | 8 + pkg/baremetal/tasks/ipmiprobe.go | 227 ++++++++++ pkg/baremetal/utils/ipmitool/ipmitool.go | 9 +- pkg/cloudcommon/agent/agent.go | 97 ++++- pkg/cloudcommon/agent/storagecache.go | 59 +++ pkg/cloudcommon/db/external.go | 2 +- pkg/cloudcommon/options/options.go | 2 + pkg/cloudcommon/types/ipmi.go | 26 +- pkg/cloudcommon/types/types.go | 3 + pkg/compute/guestdrivers/baremetals.go | 26 +- pkg/compute/guestdrivers/virtualization.go | 6 +- pkg/compute/hostdrivers/baremetal.go | 36 +- pkg/compute/models/baremetalagents.go | 43 ++ pkg/compute/models/guest_actions.go | 18 + pkg/compute/models/guestdrivers.go | 2 +- pkg/compute/models/guestnetworks.go | 2 +- pkg/compute/models/guests.go | 6 +- pkg/compute/models/hosts.go | 385 ++++++++++++++-- pkg/compute/models/loadbalancernetworks.go | 7 +- pkg/compute/models/networks.go | 112 ++--- pkg/compute/models/reservedips.go | 51 ++- pkg/compute/models/storagecaches.go | 31 +- pkg/compute/tasks/baremetal_create_task.go | 74 ++++ .../tasks/baremetal_ipmi_probe_task.go | 62 +++ pkg/compute/tasks/eip_allocate_task.go | 3 +- pkg/hostman/hostinfo/hostinfo.go | 2 +- pkg/hostman/hostutils/hostutils.go | 12 +- pkg/hostman/storageman/core.go | 8 +- pkg/hostman/storageman/disk_base.go | 4 +- pkg/hostman/storageman/disk_local.go | 2 +- pkg/hostman/storageman/disk_rbd.go | 2 +- .../storageman/imagecachemanager_base.go | 6 +- .../storageman/imagecachemanager_local.go | 8 +- .../storageman/imagecachemanager_rbd.go | 4 +- pkg/hostman/storageman/storage_base.go | 6 +- pkg/hostman/storageman/storage_local.go | 6 +- pkg/hostman/storageman/storage_nas.go | 2 +- pkg/hostman/storageman/storage_nfs.go | 2 +- pkg/hostman/storageman/storage_rbd.go | 6 +- pkg/mcclient/modules/mod_hosts.go | 2 +- pkg/mcclient/modules/mod_reservedips.go | 2 +- pkg/notify/cache/doc.go | 14 + pkg/notify/interface/doc.go | 14 + pkg/notify/rpc/apis/doc.go | 14 + pkg/notify/rpc/doc.go | 14 + pkg/util/billing/billingcycle.go | 42 +- pkg/util/httputils/fs.go | 38 ++ pkg/util/redfish/bmconsole/bmconsole.go | 73 ++++ pkg/util/redfish/bmconsole/doc.go | 15 + pkg/util/redfish/bmconsole/idrac.go | 94 ++++ pkg/util/redfish/bmconsole/ilo.go | 86 ++++ pkg/util/redfish/doc.go | 15 + pkg/util/redfish/driver.go | 25 +- pkg/util/redfish/generic/doc.go | 15 + pkg/util/redfish/generic/generic.go | 14 + pkg/util/redfish/hprest/doc.go | 15 + pkg/util/redfish/hprest/hprest.go | 24 +- pkg/util/redfish/idrac/doc.go | 15 + pkg/util/redfish/idrac/idrac.go | 92 +--- pkg/util/redfish/ilo/doc.go | 15 + pkg/util/redfish/ilo/ilo.go | 111 +++-- pkg/util/redfish/loader/doc.go | 15 + pkg/util/redfish/loader/loader.go | 14 + pkg/util/redfish/redfish.go | 104 ++++- pkg/util/redfish/resource.go | 14 + pkg/util/redfish/utils.go | 55 +++ 88 files changed, 2777 insertions(+), 502 deletions(-) create mode 100644 pkg/baremetal/tasks/ipmiprobe.go create mode 100644 pkg/cloudcommon/agent/storagecache.go create mode 100644 pkg/compute/tasks/baremetal_create_task.go create mode 100644 pkg/compute/tasks/baremetal_ipmi_probe_task.go create mode 100644 pkg/util/httputils/fs.go create mode 100644 pkg/util/redfish/bmconsole/bmconsole.go create mode 100644 pkg/util/redfish/bmconsole/doc.go create mode 100644 pkg/util/redfish/bmconsole/idrac.go create mode 100644 pkg/util/redfish/bmconsole/ilo.go create mode 100644 pkg/util/redfish/doc.go create mode 100644 pkg/util/redfish/generic/doc.go create mode 100644 pkg/util/redfish/hprest/doc.go create mode 100644 pkg/util/redfish/idrac/doc.go create mode 100644 pkg/util/redfish/ilo/doc.go create mode 100644 pkg/util/redfish/loader/doc.go create mode 100644 pkg/util/redfish/utils.go 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) +}