diff --git a/cmd/climc/shell/baremetalevents.go b/cmd/climc/shell/baremetalevents.go new file mode 100644 index 0000000000..f6987909a9 --- /dev/null +++ b/cmd/climc/shell/baremetalevents.go @@ -0,0 +1,71 @@ +// 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 ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +func init() { + type BaremetalEventListOptions struct { + HostId string `help:"filter by hostId"` + HostName string `help:"filter by hostname"` + IpmiIp string `help:"filter by ipmi_ip"` + Type string `help:"filter by type" choices:"system|manager"` + PagingMarker string `help:"marker for pagination"` + Limit int `help:"page limit, default 20" default:"20"` + Severity string `help:"filter by severity"` + } + R(&BaremetalEventListOptions{}, "baremetal-event-list", "List baremetal events", func(s *mcclient.ClientSession, args *BaremetalEventListOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.BaremetalEvents.List(s, params) + if err != nil { + return err + } + printList(result, modules.BaremetalEvents.GetColumns(s)) + return nil + }) + + type BaremetalEventCreateOptions struct { + HOST string `json:"-" help:"Host Id"` + Type string `json:"type" help:"event type" choices:"system|manager"` + EventId string `json:"event_id" help:"original event Id"` + MESSAGE string `json:"message" help:"content of event"` + Severity string `json:"severity" help:"Severity of event"` + CREATED string `json:"created" help:"when event was created"` + } + R(&BaremetalEventCreateOptions{}, "baremetal-event-create", "Create baremetal event", func(s *mcclient.ClientSession, args *BaremetalEventCreateOptions) error { + host, err := modules.Hosts.Get(s, args.HOST, nil) + if err != nil { + return err + } + hostId, _ := host.Get("id") + hostName, _ := host.Get("name") + ipmiIp, _ := host.Get("ipmi_ip") + params := jsonutils.Marshal(args).(*jsonutils.JSONDict) + params.Add(hostId, "host_id") + params.Add(hostName, "host_name") + params.Add(ipmiIp, "ipmi_ip") + result, err := modules.BaremetalEvents.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) +} diff --git a/cmd/redfishcli/shell/manager.go b/cmd/redfishcli/shell/manager.go index 2c32b4b51b..30b31e9090 100644 --- a/cmd/redfishcli/shell/manager.go +++ b/cmd/redfishcli/shell/manager.go @@ -17,8 +17,10 @@ package shell import ( "context" "fmt" + "time" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/printutils" @@ -79,9 +81,18 @@ func init() { }) type ReadLogsOptions struct { + Since string `help:"read logs after, format: YYYY-MM-DD"` } shellutils.R(&ReadLogsOptions{}, "system-logs", "Read system logs", func(cli redfish.IRedfishDriver, args *ReadLogsOptions) error { - events, err := cli.ReadSystemLogs(context.Background()) + var sinceTm time.Time + var err error + if len(args.Since) > 0 { + sinceTm, err = timeutils.ParseTimeStr(args.Since) + if err != nil { + return err + } + } + events, err := cli.ReadSystemLogs(context.Background(), sinceTm) if err != nil { return err } @@ -89,7 +100,15 @@ func init() { return nil }) shellutils.R(&ReadLogsOptions{}, "manager-logs", "Read manager logs", func(cli redfish.IRedfishDriver, args *ReadLogsOptions) error { - events, err := cli.ReadManagerLogs(context.Background()) + var sinceTm time.Time + var err error + if len(args.Since) > 0 { + sinceTm, err = timeutils.ParseTimeStr(args.Since) + if err != nil { + return err + } + } + events, err := cli.ReadManagerLogs(context.Background(), sinceTm) if err != nil { return err } diff --git a/pkg/baremetal/cronjobs.go b/pkg/baremetal/cronjobs.go new file mode 100644 index 0000000000..d9b651f6d0 --- /dev/null +++ b/pkg/baremetal/cronjobs.go @@ -0,0 +1,251 @@ +// 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 baremetal + +import ( + "context" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/timeutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/cloudcommon/consts" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/influxdb" + "yunion.io/x/onecloud/pkg/util/redfish" +) + +type IBaremetalCronJob interface { + Name() string + NeedsToRun(now time.Time) bool + StartRun() + Do(ctx context.Context, now time.Time) error + StopRun() +} + +func DoCronJobs(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + if isStart { + return + } + for _, bm := range GetBaremetalManager().GetBaremetals() { + if bm.GetTask() == nil { + bm.doCronJobs(ctx) + } + } +} + +type SBaseBaremetalCronJob struct { + lastTime time.Time + running bool + interval time.Duration + baremetal *SBaremetalInstance +} + +func (job *SBaseBaremetalCronJob) NeedsToRun(now time.Time) bool { + if (job.lastTime.IsZero() || now.Sub(job.lastTime) > job.interval) && !job.running { + return true + } + return false +} + +func (job *SBaseBaremetalCronJob) StartRun() { + job.running = true +} + +func (job *SBaseBaremetalCronJob) StopRun() { + job.running = false +} + +type SStatusProbeJob struct { + SBaseBaremetalCronJob +} + +func NewStatusProbeJob(baremetal *SBaremetalInstance, interval time.Duration) IBaremetalCronJob { + return &SStatusProbeJob{ + SBaseBaremetalCronJob: SBaseBaremetalCronJob{ + baremetal: baremetal, + interval: interval, + }, + } +} + +func (job *SStatusProbeJob) Name() string { + return "StatusProbeJob" +} + +func (job *SStatusProbeJob) Do(ctx context.Context, now time.Time) error { + bStatus := job.baremetal.GetStatus() + if bStatus == api.BAREMETAL_READY || bStatus == api.BAREMETAL_RUNNING { + ps, err := job.baremetal.GetPowerStatus() + if err != nil { + return errors.Wrap(err, "GetPowerStatus") + } + job.lastTime = now + pps := PowerStatusToBaremetalStatus(ps) + if pps != bStatus { + log.Debugf("Detected baremetal status change!") + job.baremetal.SyncAllStatus(ps) + return nil + } + } + return nil +} + +type SLogFetchJob struct { + SBaseBaremetalCronJob +} + +func NewLogFetchJob(baremetal *SBaremetalInstance, interval time.Duration) IBaremetalCronJob { + return &SLogFetchJob{ + SBaseBaremetalCronJob: SBaseBaremetalCronJob{ + baremetal: baremetal, + interval: interval, + }, + } +} + +func (job *SLogFetchJob) Name() string { + return "LogFetchJob" +} + +func eventToJson(e redfish.SEvent) *jsonutils.JSONDict { + json := jsonutils.NewDict() + json.Add(jsonutils.NewString(timeutils.IsoTime(e.Created)), "created") + json.Add(jsonutils.NewString(e.EventId), "event_id") + json.Add(jsonutils.NewString(e.Message), "message") + json.Add(jsonutils.NewString(e.Severity), "severity") + json.Add(jsonutils.NewString(e.Type), "type") + return json +} + +func fetchLogs(baremetal *SBaremetalInstance, ctx context.Context, logType string) error { + params := jsonutils.NewDict() + s := auth.GetAdminSession(ctx, consts.GetRegion(), "") + params.Add(jsonutils.NewInt(1), "limit") + params.Add(jsonutils.NewString(baremetal.GetId()), "host_id") + params.Add(jsonutils.NewString(logType), "type") + result, err := modules.BaremetalEvents.List(s, params) + if err != nil { + return errors.Wrap(err, "modules.BaremetalEvents.List") + } + log.Debugf("%s", result.Data) + var eventId string + var since time.Time + if len(result.Data) > 0 { + since, _ = result.Data[0].GetTime("created") + eventId, _ = result.Data[0].GetString("event_id") + } + offset := 0 + logs, err := baremetal.fetchLogs(ctx, logType, since) + if err != nil { + return errors.Wrap(err, "job.baremetal.FetchLogs") + } + if len(eventId) > 0 { + // latset eventId + for i := range logs { + if logs[i].EventId == eventId { + offset = i + 1 + break + } + } + } + for i := offset; i < len(logs); i += 1 { + eventData := eventToJson(logs[i]) + eventData.Add(jsonutils.NewString(baremetal.GetId()), "host_id") + eventData.Add(jsonutils.NewString(baremetal.GetName()), "host_name") + eventData.Add(jsonutils.NewString(baremetal.GetIPMINicIPAddr()), "ipmi_ip") + log.Debugf("%s", eventData) + _, err := modules.BaremetalEvents.Create(s, eventData) + if err != nil { + return errors.Wrap(err, "modules.BaremetalEvents.Create") + } + } + return nil +} + +func (job *SLogFetchJob) Do(ctx context.Context, now time.Time) error { + if !job.baremetal.isRedfishCapable() { + return nil + } + err := fetchLogs(job.baremetal, ctx, redfish.EVENT_TYPE_SYSTEM) + if err != nil { + return errors.Wrap(err, "fetchLogs api.EVENT_TYPE_SYSTEM") + } + err = fetchLogs(job.baremetal, ctx, redfish.EVENT_TYPE_MANAGER) + if err != nil { + return errors.Wrap(err, "fetchLogs api.EVENT_TYPE_MANAGER") + } + job.lastTime = now + return nil +} + +type SSendMetricsJob struct { + SBaseBaremetalCronJob +} + +func NewSendMetricsJob(baremetal *SBaremetalInstance, interval time.Duration) IBaremetalCronJob { + return &SSendMetricsJob{ + SBaseBaremetalCronJob: SBaseBaremetalCronJob{ + baremetal: baremetal, + interval: interval, + }, + } +} + +func (job *SSendMetricsJob) Name() string { + return "SendMetricsJob" +} + +func (job *SSendMetricsJob) Do(ctx context.Context, now time.Time) error { + s := auth.GetAdminSession(ctx, consts.GetRegion(), "") + urls, err := s.GetServiceURLs("influxdb", o.Options.SessionEndpointType) + if err != nil { + return errors.Wrap(err, "s.GetServiceURLs") + } + if len(urls) == 0 { + return nil + } + if !job.baremetal.isRedfishCapable() { + return nil + } + powerMetrics, thermalMetrics, err := job.baremetal.fetchPowerThermalMetrics(ctx) + tags := job.baremetal.getTags() + metrics := []influxdb.SMetricData{ + { + Name: "power", + Tags: tags, + Metrics: powerMetrics, + Timestamp: now, + }, + { + Name: "thermal", + Tags: tags, + Metrics: thermalMetrics, + Timestamp: now, + }, + } + err = influxdb.SendMetrics(urls, "telegraf", metrics, false) + if err != nil { + return errors.Wrap(err, "influxdb.SendMetrics") + } + job.lastTime = now + return nil +} diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index 2a7ac18bf1..855ada5f6d 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "strings" "sync" "time" @@ -61,6 +62,7 @@ import ( "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/influxdb" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/redfish" "yunion.io/x/onecloud/pkg/util/ssh" @@ -98,6 +100,10 @@ func (m *SBaremetalManager) GetZoneId() string { return m.Agent.Zone.Id } +func (m *SBaremetalManager) GetZoneName() string { + return m.Agent.Zone.Name +} + func (m *SBaremetalManager) loadConfigs() error { m.killAllIPMITool() files, err := ioutil.ReadDir(m.configPath) @@ -469,6 +475,8 @@ type SBaremetalInstance struct { taskQueue *tasks.TaskQueue server baremetaltypes.IBaremetalServer serverLock *sync.Mutex + + cronJobs []IBaremetalCronJob } func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*SBaremetalInstance, error) { @@ -479,6 +487,11 @@ func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*S taskQueue: tasks.NewTaskQueue(), serverLock: new(sync.Mutex), } + bm.cronJobs = []IBaremetalCronJob{ + NewStatusProbeJob(bm, time.Duration(o.Options.StatusProbeIntervalSeconds)*time.Second), + NewLogFetchJob(bm, time.Duration(o.Options.LogFetchIntervalSeconds)*time.Second), + NewSendMetricsJob(bm, time.Duration(o.Options.SendMetricsIntervalSeconds)*time.Second), + } err := os.MkdirAll(bm.GetDir(), 0755) if err != nil { return nil, err @@ -1061,7 +1074,7 @@ LABEL start resp += " APPEND hd0 0\n" b.ClearSSHConfig() } - log.Infof("[TFTP response]: \n%s", resp) + // log.Debugf("[SysLinux config]: \n%s", resp) return resp } @@ -1295,11 +1308,25 @@ func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI { return ipmitool.NewLanPlusIPMI(conf.IpAddr, conf.Username, conf.Password) } -func (b *SBaremetalInstance) GetRedfishCli(ctx context.Context) redfish.IRedfishDriver { +func (b *SBaremetalInstance) isRedfishCapable() bool { conf := b.GetIPMIConfig() if conf == nil { + return false + } + if !conf.Verified { + return false + } + if !conf.RedfishApi { + return false + } + return true +} + +func (b *SBaremetalInstance) GetRedfishCli(ctx context.Context) redfish.IRedfishDriver { + if !b.isRedfishCapable() { return nil } + conf := b.GetIPMIConfig() return redfish.NewRedfishDriver(ctx, "https://"+conf.IpAddr, conf.Username, conf.Password, false) } @@ -1374,10 +1401,57 @@ func (b *SBaremetalInstance) GetZoneId() string { return b.manager.GetZoneId() } +func (b *SBaremetalInstance) GetZoneName() string { + return b.manager.GetZoneName() +} + func (b *SBaremetalInstance) GetStorageCacheId() string { return b.manager.Agent.CacheManager.GetId() } +func (b *SBaremetalInstance) GetBootMode() string { + bootMode, _ := b.desc.GetString("boot_mode") + if len(bootMode) == 0 { + bootMode = api.BOOT_MODE_PXE + } + return bootMode +} + +func (b *SBaremetalInstance) GetRegion() string { + r, _ := b.desc.GetString("region") + return r +} + +func (b *SBaremetalInstance) GetRegionId() string { + r, _ := b.desc.GetString("region_id") + return r +} + +func (b *SBaremetalInstance) GetSerialNumber() string { + r, _ := b.desc.GetString("sn") + return r +} + +func (b *SBaremetalInstance) GetManufacture() string { + r, _ := b.desc.GetString("sys_info", "manufacture") + return r +} + +func (b *SBaremetalInstance) GetModel() string { + r, _ := b.desc.GetString("sys_info", "model") + return r +} + +func (b *SBaremetalInstance) GetNodeCount() string { + r, _ := b.desc.GetString("node_count") + return r +} + +func (b *SBaremetalInstance) GetMemGb() string { + memMb, _ := b.desc.Int("mem_size") + return strconv.FormatInt(memMb/1024, 10) +} + func (b *SBaremetalInstance) DelayedRemove(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) { b.remove() return nil, nil @@ -1750,6 +1824,146 @@ func (b *SBaremetalInstance) DoNTPConfig() error { return nil } +func (b *SBaremetalInstance) fetchLogs(ctx context.Context, logType string, since time.Time) ([]redfish.SEvent, error) { + redfishApi := b.GetRedfishCli(ctx) + if redfishApi == nil { + // errors.Wrap(httperrors.ErrNotSupported, "no valid redfish api") + return nil, nil + } + switch logType { + case redfish.EVENT_TYPE_SYSTEM: + return redfishApi.ReadSystemLogs(ctx, since) + case redfish.EVENT_TYPE_MANAGER: + return redfishApi.ReadManagerLogs(ctx, since) + } + return nil, errors.Wrap(httperrors.ErrNotSupported, logType) +} + +func (b *SBaremetalInstance) clearLogs(ctx context.Context, logType string) error { + redfishApi := b.GetRedfishCli(ctx) + if redfishApi == nil { + return errors.Wrap(httperrors.ErrNotSupported, "no valid redfish api") + } + switch logType { + case redfish.EVENT_TYPE_SYSTEM: + return redfishApi.ClearSystemLogs(ctx) + case redfish.EVENT_TYPE_MANAGER: + return redfishApi.ClearManagerLogs(ctx) + } + return errors.Wrap(httperrors.ErrNotSupported, logType) +} + +func (b *SBaremetalInstance) doCronJobs(ctx context.Context) { + for _, job := range b.cronJobs { + now := time.Now().UTC() + if job.NeedsToRun(now) { + log.Debugf("need to run %s", job.Name()) + func() { + job.StartRun() + defer job.StopRun() + err := job.Do(ctx, now) + if err != nil { + log.Errorf("Baremetal %s do cronjob %s fail: %s", b.GetName(), job.Name(), err) + } + }() + } + } +} + +func (b *SBaremetalInstance) fetchPowerThermalMetrics(ctx context.Context) ([]influxdb.SKeyValue, []influxdb.SKeyValue, error) { + redfishApi := b.GetRedfishCli(ctx) + if redfishApi == nil { + return nil, nil, errors.Wrap(httperrors.ErrNotSupported, "no valid redfish api") + } + powers, err := redfishApi.GetPower(ctx) + if err != nil { + return nil, nil, errors.Wrap(err, "redfishApi.GetPower") + } + thermals, err := redfishApi.GetThermal(ctx) + if err != nil { + return nil, nil, errors.Wrap(err, "redfishApi.Thermal") + } + powerMetrics := powers[0].ToMetrics() + thermalMetrics := make([]influxdb.SKeyValue, 0) + for _, t := range thermals { + thermalMetrics = append(thermalMetrics, t.ToMetric()) + } + return powerMetrics, thermalMetrics, nil +} + +func (b *SBaremetalInstance) getTags() []influxdb.SKeyValue { + tags := []influxdb.SKeyValue{ + { + Key: "id", + Value: b.GetId(), + }, + { + Key: "name", + Value: b.GetName(), + }, + { + Key: "zone_id", + Value: b.GetZoneId(), + }, + { + Key: "zone", + Value: b.GetZoneName(), + }, + { + Key: "boot_mode", + Value: b.GetBootMode(), + }, + { + Key: "host_type", + Value: "baremetal", + }, + { + Key: "region", + Value: b.GetRegion(), + }, + { + Key: "region_id", + Value: b.GetRegionId(), + }, + { + Key: "sn", + Value: b.GetSerialNumber(), + }, + { + Key: "manufacture", + Value: b.GetManufacture(), + }, + { + Key: "model", + Value: b.GetModel(), + }, + { + Key: "status", + Value: b.GetStatus(), + }, + { + Key: "ncpu", + Value: b.GetNodeCount(), + }, + { + Key: "mem_gb", + Value: b.GetMemGb(), + }, + } + srvId := b.GetServerId() + if len(srvId) > 0 { + tags = append(tags, influxdb.SKeyValue{ + Key: "server_id", + Value: srvId, + }) + tags = append(tags, influxdb.SKeyValue{ + Key: "server", + Value: b.GetServerName(), + }) + } + return tags +} + type SBaremetalServer struct { baremetal *SBaremetalInstance desc *jsonutils.JSONDict diff --git a/pkg/baremetal/options/options.go b/pkg/baremetal/options/options.go index bd68ef6af0..cea6791e25 100644 --- a/pkg/baremetal/options/options.go +++ b/pkg/baremetal/options/options.go @@ -46,6 +46,10 @@ type BaremetalOptions struct { CachePath string `help:"local image cache directory"` EnablePxeBoot bool `help:"Enable DHCP PXE boot" default:"true"` BootIsoPath string `help:"iso boot image path"` + + StatusProbeIntervalSeconds int `help:"interval to probe baremetal status, default is 60 seconds" default:"60"` + LogFetchIntervalSeconds int `help:"interval to fetch baremetal log, default is 900 seconds" default:"900"` + SendMetricsIntervalSeconds int `help:"interval to send baremetal metrics, default is 300 seconds" default:"300"` } var ( diff --git a/pkg/baremetal/service/service.go b/pkg/baremetal/service/service.go index 82bc37f8eb..de1b5b4f76 100644 --- a/pkg/baremetal/service/service.go +++ b/pkg/baremetal/service/service.go @@ -17,6 +17,7 @@ package service import ( "os" "path/filepath" + "time" "yunion.io/x/log" @@ -26,6 +27,7 @@ import ( o "yunion.io/x/onecloud/pkg/baremetal/options" "yunion.io/x/onecloud/pkg/baremetal/tasks" app_common "yunion.io/x/onecloud/pkg/cloudcommon/app" + "yunion.io/x/onecloud/pkg/cloudcommon/cronman" common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" "yunion.io/x/onecloud/pkg/cloudcommon/service" "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" @@ -66,6 +68,11 @@ func (s *BaremetalService) StartService() { s.startAgent(app) + cron := cronman.InitCronJobManager(false, o.Options.CronJobWorkerCount) + cron.AddJobAtIntervals("BaremetalCronJobs", 10*time.Second, baremetal.DoCronJobs) + cron.Start() + defer cron.Stop() + app_common.ServeForeverWithCleanup(app, &o.Options.BaseOptions, func() { tasks.OnStop() baremetal.Stop() diff --git a/pkg/logger/models/baremetalevents.go b/pkg/logger/models/baremetalevents.go new file mode 100644 index 0000000000..2451873a13 --- /dev/null +++ b/pkg/logger/models/baremetalevents.go @@ -0,0 +1,104 @@ +// 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 models + +import ( + "fmt" + "time" + + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SBaremetalEventManager struct { + db.SModelBaseManager +} + +type SBaremetalEvent struct { + db.SModelBase + + Id int64 `primary:"true" auto_increment:"true" list:"user"` + HostId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"` + HostName string `width:"64" charset:"utf8" nullable:"false" list:"user" create:"required"` + IpmiIp string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"optional"` + Created time.Time `nullable:"false" create:"required" list:"user"` + EventId string `width:"32" nullable:"true" create:"optional" list:"user"` + Type string `width:"10" nullable:"true" create:"optional" list:"user"` + + Message string `nullable:"false" create:"required" list:"user"` + Severity string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"optional"` +} + +var BaremetalEventManager *SBaremetalEventManager + +func init() { + BaremetalEventManager = &SBaremetalEventManager{ + SModelBaseManager: db.NewModelBaseManager( + SBaremetalEvent{}, + "baremetal_event_tbl", + "baremetalevent", + "baremetalevents", + ), + } + BaremetalEventManager.SetVirtualObject(BaremetalEventManager) +} + +func (event *SBaremetalEvent) GetId() string { + return fmt.Sprintf("%d", event.Id) +} + +func (event *SBaremetalEvent) GetName() string { + return event.HostName + event.EventId +} + +func (event *SBaremetalEvent) GetModelManager() db.IModelManager { + return BaremetalEventManager +} + +func (manager *SBaremetalEventManager) ResourceScope() rbacutils.TRbacScope { + return rbacutils.ScopeSystem +} + +func (manager *SBaremetalEventManager) GetPagingConfig() *db.SPagingConfig { + return &db.SPagingConfig{ + Order: sqlchemy.SQL_ORDER_DESC, + MarkerField: "id", + DefaultLimit: 20, + } +} + +/*func (manager *SBaremetalEventManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + hostId, _ := data.GetString("host_id") + eventId, _ := data.GetString("event_id") + q := manager.Query().Equals("host_id", hostId).Equals("event_id", eventId) + cnt, err := q.CountWithError() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if cnt > 0 { + return nil, httperrors.NewConflictError("duplicate log") + } + return manager.SModelBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) +}*/ + +/*func (event *SBaremetalEvent) ValidateDeleteCondition(ctx context.Context) error { + return httperrors.NewForbiddenError("read only event") +}*/ + +/*func (event *SBaremetalEvent) ValidateUpdateCondition(ctx context.Context) error { + return httperrors.NewForbiddenError("read only event") +}*/ diff --git a/pkg/logger/service/handlers.go b/pkg/logger/service/handlers.go index 162c3480d9..66f122d620 100644 --- a/pkg/logger/service/handlers.go +++ b/pkg/logger/service/handlers.go @@ -33,6 +33,7 @@ func initHandlers(app *appsrv.Application) { for _, manager := range []db.IModelManager{ models.ActionLog, + models.BaremetalEventManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/mcclient/modules/mod_baremetal_events.go b/pkg/mcclient/modules/mod_baremetal_events.go new file mode 100644 index 0000000000..abe2a99478 --- /dev/null +++ b/pkg/mcclient/modules/mod_baremetal_events.go @@ -0,0 +1,31 @@ +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + BaremetalEvents modulebase.ResourceManager +) + +func init() { + BaremetalEvents = NewActionManager("baremetalevent", "baremetalevents", + []string{"id", "host_id", "host_name", + "ipmi_ip", "created", "event_id", + "message", "severity", "type", + }, + []string{}) + register(&BaremetalEvents) +} diff --git a/pkg/util/influxdb/metrics.go b/pkg/util/influxdb/metrics.go new file mode 100644 index 0000000000..e32b31e995 --- /dev/null +++ b/pkg/util/influxdb/metrics.go @@ -0,0 +1,71 @@ +// 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 influxdb + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" +) + +type SKeyValue struct { + Key string + Value string +} + +func (kv SKeyValue) String() string { + return fmt.Sprintf("%s=%s", kv.Key, strings.ReplaceAll(kv.Value, " ", "+")) +} + +type TKeyValuePairs []SKeyValue + +func (a TKeyValuePairs) Len() int { return len(a) } +func (a TKeyValuePairs) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a TKeyValuePairs) Less(i, j int) bool { return a[i].Key < a[j].Key } + +type SMetricData struct { + Name string + Tags []SKeyValue + Metrics []SKeyValue + Timestamp time.Time +} + +func (m *SMetricData) Line() string { + sort.Sort(TKeyValuePairs(m.Tags)) + sort.Sort(TKeyValuePairs(m.Metrics)) + + line := strings.Builder{} + line.WriteString(m.Name) + for i := range m.Tags { + line.WriteByte(',') + line.WriteString(m.Tags[i].String()) + } + line.WriteByte(' ') + for i := range m.Metrics { + if i > 0 { + line.WriteByte(',') + } + line.WriteString(m.Metrics[i].String()) + } + line.WriteByte(' ') + if m.Timestamp.IsZero() { + m.Timestamp = time.Now() + } + line.WriteString(strconv.FormatInt(m.Timestamp.UnixNano()/1000000, 10)) + + return line.String() +} diff --git a/pkg/util/influxdb/send.go b/pkg/util/influxdb/send.go new file mode 100644 index 0000000000..ea1ddb79f3 --- /dev/null +++ b/pkg/util/influxdb/send.go @@ -0,0 +1,40 @@ +// 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 influxdb + +import ( + "yunion.io/x/pkg/errors" +) + +func SendMetrics(urls []string, dbName string, metrics []SMetricData, debug bool) error { + lines := make([]string, len(metrics)) + for i := range metrics { + lines[i] = metrics[i].Line() + } + for _, url := range urls { + db := NewInfluxdbWithDebug(url, debug) + err := db.SetDatabase(dbName) + if err != nil { + return errors.Wrap(err, "SetDatabase") + } + for _, line := range lines { + err = db.Write(line, "ms") + if err != nil { + return errors.Wrap(err, "db.Write") + } + } + } + return nil +} diff --git a/pkg/util/redfish/driver.go b/pkg/util/redfish/driver.go index 8c3598b1b4..c9eb82666d 100644 --- a/pkg/util/redfish/driver.go +++ b/pkg/util/redfish/driver.go @@ -16,6 +16,7 @@ package redfish import ( "context" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -59,8 +60,12 @@ type IRedfishDriver interface { Reset(ctx context.Context, action string) error - ReadSystemLogs(ctx context.Context) ([]SEvent, error) - ReadManagerLogs(ctx context.Context) ([]SEvent, error) + GetSystemLogsPath() string + GetManagerLogsPath() string + GetClearSystemLogsPath() string + GetClearManagerLogsPath() string + ReadSystemLogs(ctx context.Context, since time.Time) ([]SEvent, error) + ReadManagerLogs(ctx context.Context, since time.Time) ([]SEvent, error) ClearSystemLogs(ctx context.Context) error ClearManagerLogs(ctx context.Context) error @@ -71,6 +76,8 @@ type IRedfishDriver interface { GetIndicatorLED(ctx context.Context) (bool, error) SetIndicatorLED(ctx context.Context, on bool) error + GetPowerPath() string + GetThermalPath() string GetPower(ctx context.Context) ([]SPower, error) GetThermal(ctx context.Context) ([]STemperature, error) diff --git a/pkg/util/redfish/hprest/hprest.go b/pkg/util/redfish/hprest/hprest.go index 70902dc449..6d1a464168 100644 --- a/pkg/util/redfish/hprest/hprest.go +++ b/pkg/util/redfish/hprest/hprest.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -210,7 +211,7 @@ func (r *SHpRestApi) BmcReset(ctx context.Context) error { return nil } -func (r *SHpRestApi) readLogs(ctx context.Context, subsys string) ([]redfish.SEvent, error) { +func (r *SHpRestApi) readLogs(ctx context.Context, subsys string, since time.Time) ([]redfish.SEvent, error) { _, manager, err := r.GetResource(ctx, subsys, "0", "Logs", "0") if err != nil { return nil, errors.Wrap(err, "GetResource Managers 0") @@ -236,7 +237,17 @@ func (r *SHpRestApi) readLogs(ctx context.Context, subsys string) ([]redfish.SEv if err != nil { return nil, errors.Wrap(err, "resp.Unmarshal") } - events = append(events, tmpEvents...) + timeBreak := false + for i := range tmpEvents { + if !since.IsZero() && tmpEvents[i].Created.Before(since) { + timeBreak = true + break + } + events = append(events, tmpEvents[i]) + } + if timeBreak { + break + } nextPage, _ := resp.Int("links", "NextPage", "page") if nextPage > 0 { nextPath = fmt.Sprintf("%s?page=%d", path, nextPage) @@ -247,12 +258,12 @@ func (r *SHpRestApi) readLogs(ctx context.Context, subsys string) ([]redfish.SEv return events, nil } -func (r *SHpRestApi) ReadSystemLogs(ctx context.Context) ([]redfish.SEvent, error) { - return r.readLogs(ctx, "Systems") +func (r *SHpRestApi) ReadSystemLogs(ctx context.Context, since time.Time) ([]redfish.SEvent, error) { + return r.readLogs(ctx, "Systems", since) } -func (r *SHpRestApi) ReadManagerLogs(ctx context.Context) ([]redfish.SEvent, error) { - return r.readLogs(ctx, "Managers") +func (r *SHpRestApi) ReadManagerLogs(ctx context.Context, since time.Time) ([]redfish.SEvent, error) { + return r.readLogs(ctx, "Managers", since) } func (r *SHpRestApi) clearLogs(ctx context.Context, subsys string) error { diff --git a/pkg/util/redfish/idrac/idrac.go b/pkg/util/redfish/idrac/idrac.go index 519aa51231..ac4399db2a 100644 --- a/pkg/util/redfish/idrac/idrac.go +++ b/pkg/util/redfish/idrac/idrac.go @@ -333,3 +333,27 @@ func (r *SIDracRefishApi) GetConsoleJNLP(ctx context.Context) (string, error) { bmc := bmconsole.NewBMCConsole(r.GetHost(), r.GetUsername(), r.GetPassword(), r.IsDebug) return bmc.GetIdracConsoleJNLP(ctx, "", "") } + +func (r *SIDracRefishApi) GetSystemLogsPath() string { + return "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel" +} + +func (r *SIDracRefishApi) GetManagerLogsPath() string { + return "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Lclog" +} + +func (r *SIDracRefishApi) GetClearSystemLogsPath() string { + return "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog" +} + +func (r *SIDracRefishApi) GetClearManagerLogsPath() string { + return "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Lclog/Actions/LogService.ClearLog" +} + +func (r *SIDracRefishApi) GetPowerPath() string { + return "/redfish/v1/Chassis/System.Embedded.1/Power" +} + +func (r *SIDracRefishApi) GetThermalPath() string { + return "/redfish/v1/Chassis/System.Embedded.1/Thermal" +} diff --git a/pkg/util/redfish/ilo/ilo.go b/pkg/util/redfish/ilo/ilo.go index c335d2fe7c..fda939a7e5 100644 --- a/pkg/util/redfish/ilo/ilo.go +++ b/pkg/util/redfish/ilo/ilo.go @@ -17,6 +17,7 @@ package ilo import ( "context" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -118,10 +119,13 @@ func (r *SILORefishApi) SetNextBootVirtualCdrom(ctx context.Context) error { return nil } -func (r *SILORefishApi) readLogs(ctx context.Context, subsys string) ([]redfish.SEvent, error) { - path, _, err := r.GetResource(ctx, subsys, "0", "LogServices", "0", "Entries") - if err != nil { - return nil, errors.Wrap(err, "GetResource") +func (r *SILORefishApi) readLogs(ctx context.Context, path string, subsys string, typeStr string, since time.Time) ([]redfish.SEvent, error) { + if len(path) == 0 { + var err error + path, _, err = r.GetResource(ctx, subsys, "0", "LogServices", "0", "Entries") + if err != nil { + return nil, errors.Wrap(err, "GetResource") + } } resp, err := r.Get(ctx, path) if err != nil { @@ -132,7 +136,7 @@ func (r *SILORefishApi) readLogs(ctx context.Context, subsys string) ([]redfish. return nil, errors.Wrap(err, "GetArray") } events := make([]redfish.SEvent, 0) - for idx := range paths { + for idx := len(paths) - 1; idx >= 0; idx -= 1 { eventPath, err := paths[idx].GetString(r.IRedfishDriver().LinkKey()) if err != nil { return nil, errors.Wrap(err, "GetString") @@ -147,29 +151,42 @@ func (r *SILORefishApi) readLogs(ctx context.Context, subsys string) ([]redfish. if err != nil { return nil, errors.Wrap(err, "eventResp.Unmarshal") } + if !since.IsZero() && tmpEvent.Created.Before(since) { + break + } tmpEvent.EventId, _ = eventResp.GetString("Oem", "Hp", "EventNumber") if len(tmpEvent.EventId) == 0 { tmpEvent.EventId, _ = eventResp.GetString("Oem", "Hpe", "EventNumber") } + tmpEvent.Type = typeStr events = append(events, tmpEvent) } + redfish.SortEvents(events) return events, nil } -func (r *SILORefishApi) ReadSystemLogs(ctx context.Context) ([]redfish.SEvent, error) { - return r.readLogs(ctx, "Systems") +func (r *SILORefishApi) ReadSystemLogs(ctx context.Context, since time.Time) ([]redfish.SEvent, error) { + return r.readLogs(ctx, "/redfish/v1/Systems/1/LogServices/IML/Entries/", "Systems", redfish.EVENT_TYPE_SYSTEM, since) } -func (r *SILORefishApi) ReadManagerLogs(ctx context.Context) ([]redfish.SEvent, error) { - return r.readLogs(ctx, "Managers") +func (r *SILORefishApi) ReadManagerLogs(ctx context.Context, since time.Time) ([]redfish.SEvent, error) { + return r.readLogs(ctx, "/redfish/v1/Managers/1/LogServices/IEL/Entries/", "Managers", redfish.EVENT_TYPE_MANAGER, since) +} + +func (r *SILORefishApi) GetClearSystemLogsPath() string { + return "/redfish/v1/Systems/1/LogServices/IML/Actions/LogService.ClearLog/" +} + +func (r *SILORefishApi) GetClearManagerLogsPath() string { + return "/redfish/v1/Managers/1/LogServices/IEL/Actions/LogService.ClearLog/" } func (r *SILORefishApi) ClearSystemLogs(ctx context.Context) error { - return r.ClearLogs(ctx, "Systems", 0) + return r.ClearLogs(ctx, r.IRedfishDriver().GetClearManagerLogsPath(), "Systems", 0) } func (r *SILORefishApi) ClearManagerLogs(ctx context.Context) error { - return r.ClearLogs(ctx, "Managers", 0) + return r.ClearLogs(ctx, r.IRedfishDriver().GetClearManagerLogsPath(), "Managers", 0) } func (r *SILORefishApi) LogItemsKey() string { @@ -282,3 +299,11 @@ func (r *SILORefishApi) MountVirtualCdrom(ctx context.Context, path string, cdro log.Debugf("%s", resp.PrettyString()) return nil } + +func (r *SILORefishApi) GetPowerPath() string { + return "/redfish/v1/Chassis/1/Power/" +} + +func (r *SILORefishApi) GetThermalPath() string { + return "/redfish/v1/Chassis/1/Thermal/" +} diff --git a/pkg/util/redfish/redfish.go b/pkg/util/redfish/redfish.go index 3a5a745dfb..47e569b057 100644 --- a/pkg/util/redfish/redfish.go +++ b/pkg/util/redfish/redfish.go @@ -22,6 +22,7 @@ import ( "net/url" "strconv" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -190,7 +191,7 @@ func (r *SBaseRedfishClient) Probe(ctx context.Context) error { urlPath, _ := respMap[k].GetString(r.IRedfishDriver().LinkKey()) if len(urlPath) > 0 { r.Registries[k] = urlPath - log.Debugf("%s %s", k, urlPath) + // log.Debugf("%s %s", k, urlPath) } } if len(r.Registries) == 0 { @@ -522,20 +523,21 @@ func Int2str(i int) string { return strconv.FormatInt(int64(i), 10) } -func (r *SBaseRedfishClient) ReadLogs(ctx context.Context, subsys string, index int) ([]SEvent, error) { - _, manager, err := r.GetResource(ctx, subsys, "0", "LogServices", Int2str(index)) - if err != nil { - return nil, errors.Wrap(err, "GetResource Managers 0") - } - manager = r.IRedfishDriver().GetParent(manager) - path, _ := manager.GetString("Entries", r.IRedfishDriver().LinkKey()) +func (r *SBaseRedfishClient) readLogs(ctx context.Context, path string, subsys string, index int, typeStr string, since time.Time) ([]SEvent, error) { if len(path) == 0 { - return nil, errors.Wrap(err, "no entry???") + var err error + path, _, err = r.GetResource(ctx, subsys, "0", "LogServices", Int2str(index), "Entries") + if err != nil { + return nil, errors.Wrap(err, "GetResource Managers 0") + } } events := make([]SEvent, 0) for { resp, err := r.Get(ctx, path) if err != nil { + if httputils.ErrorCode(err) == 404 { + break + } return nil, errors.Wrap(err, path) } tmpEvents := make([]SEvent, 0) @@ -543,47 +545,84 @@ func (r *SBaseRedfishClient) ReadLogs(ctx context.Context, subsys string, index if err != nil { return nil, errors.Wrap(err, "resp.Unmarshal") } - events = append(events, tmpEvents...) + for i := range tmpEvents { + tmpEvents[i].Type = typeStr + } + timeBreak := false + for i := range tmpEvents { + if !since.IsZero() && tmpEvents[i].Created.Before(since) { + timeBreak = true + break + } + if !strings.Contains(tmpEvents[i].Message, "Log cleared.") { + events = append(events, tmpEvents[i]) + } + } + if timeBreak { + break + } nextPage, _ := resp.GetString("@odata.nextLink") + if len(nextPage) == 0 { + nextPage, _ = resp.GetString("Members@odata.nextLink") + } if len(nextPage) > 0 { path = nextPage } else { break } } + SortEvents(events) return events, nil } -func (r *SBaseRedfishClient) ReadSystemLogs(ctx context.Context) ([]SEvent, error) { - return r.ReadLogs(ctx, "Managers", 0) +func (r *SBaseRedfishClient) GetSystemLogsPath() string { + return "" } -func (r *SBaseRedfishClient) ReadManagerLogs(ctx context.Context) ([]SEvent, error) { - return r.ReadLogs(ctx, "Managers", 1) +func (r *SBaseRedfishClient) GetManagerLogsPath() string { + return "" } -func (r *SBaseRedfishClient) ClearLogs(ctx context.Context, subsys string, index int) error { - _, logInfo, err := r.GetResource(ctx, subsys, "0", "LogServices", Int2str(index)) - if err != nil { - return errors.Wrap(err, "GetResource Managers 0") +func (r *SBaseRedfishClient) ReadSystemLogs(ctx context.Context, since time.Time) ([]SEvent, error) { + return r.readLogs(ctx, r.IRedfishDriver().GetSystemLogsPath(), "Managers", 0, EVENT_TYPE_SYSTEM, since) +} + +func (r *SBaseRedfishClient) ReadManagerLogs(ctx context.Context, since time.Time) ([]SEvent, error) { + return r.readLogs(ctx, r.IRedfishDriver().GetManagerLogsPath(), "Managers", 1, EVENT_TYPE_MANAGER, since) +} + +func (r *SBaseRedfishClient) ClearLogs(ctx context.Context, urlPath string, subsys string, index int) error { + if len(urlPath) == 0 { + _, logInfo, err := r.GetResource(ctx, subsys, "0", "LogServices", Int2str(index)) + if err != nil { + return errors.Wrap(err, "GetResource Managers 0") + } + urlPath, err = logInfo.GetString("Actions", "#LogService.ClearLog", "target") + if err != nil { + return errors.Wrap(err, "Actions.#LogService.ClearLog.target") + } } - urlPath, err := logInfo.GetString("Actions", "#LogService.ClearLog", "target") - if err != nil { - return errors.Wrap(err, "Actions.#LogService.ClearLog.target") - } - _, _, err = r.Post(ctx, urlPath, jsonutils.NewDict()) + _, _, err := r.Post(ctx, urlPath, jsonutils.NewDict()) if err != nil { return errors.Wrap(err, "r.Post") } return nil } +func (r *SBaseRedfishClient) GetClearSystemLogsPath() string { + return "" +} + +func (r *SBaseRedfishClient) GetClearManagerLogsPath() string { + return "" +} + func (r *SBaseRedfishClient) ClearSystemLogs(ctx context.Context) error { - return r.ClearLogs(ctx, "Managers", 0) + return r.ClearLogs(ctx, r.IRedfishDriver().GetClearSystemLogsPath(), "Managers", 0) } func (r *SBaseRedfishClient) ClearManagerLogs(ctx context.Context) error { - return r.ClearLogs(ctx, "Managers", 1) + return r.ClearLogs(ctx, r.IRedfishDriver().GetClearManagerLogsPath(), "Managers", 1) } func (r *SBaseRedfishClient) GetBiosInfo(ctx context.Context) (SBiosInfo, error) { @@ -654,10 +693,25 @@ func (r *SBaseRedfishClient) SetIndicatorLED(ctx context.Context, on bool) error return r.SetIndicatorLEDInternal(ctx, "Chassis", valStr) } +func (r *SBaseRedfishClient) GetPowerPath() string { + return "" +} + +func (r *SBaseRedfishClient) GetThermalPath() string { + return "" +} + func (r *SBaseRedfishClient) GetPower(ctx context.Context) ([]SPower, error) { - _, resp, err := r.GetResource(ctx, "Chassis", "0", "Power") - if err != nil { - return nil, errors.Wrap(err, "GetResource") + path := r.IRedfishDriver().GetPowerPath() + var resp jsonutils.JSONObject + var err error + if len(path) > 0 { + resp, err = r.Get(ctx, path) + } else { + _, resp, err = r.GetResource(ctx, "Chassis", "0", "Power") + if err != nil { + return nil, errors.Wrap(err, "GetResource") + } } powers := make([]SPower, 0) err = resp.Unmarshal(&powers, "PowerControl") @@ -668,9 +722,16 @@ func (r *SBaseRedfishClient) GetPower(ctx context.Context) ([]SPower, error) { } func (r *SBaseRedfishClient) GetThermal(ctx context.Context) ([]STemperature, error) { - _, resp, err := r.GetResource(ctx, "Chassis", "0", "Thermal") - if err != nil { - return nil, errors.Wrap(err, "GetResource") + path := r.IRedfishDriver().GetThermalPath() + var resp jsonutils.JSONObject + var err error + if len(path) > 0 { + resp, err = r.Get(ctx, path) + } else { + _, resp, err = r.GetResource(ctx, "Chassis", "0", "Thermal") + if err != nil { + return nil, errors.Wrap(err, "GetResource") + } } temps := make([]STemperature, 0) err = resp.Unmarshal(&temps, "Temperatures") diff --git a/pkg/util/redfish/resource.go b/pkg/util/redfish/resource.go index 4318010472..78dfe2aaae 100644 --- a/pkg/util/redfish/resource.go +++ b/pkg/util/redfish/resource.go @@ -14,7 +14,15 @@ package redfish -import "time" +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "yunion.io/x/onecloud/pkg/util/influxdb" +) type SCdromInfo struct { Image string `json:"Image"` @@ -38,11 +46,27 @@ type SSystemInfo struct { ResetTypeSupported []string `json:"ResetTypeSupported"` } +const ( + EVENT_TYPE_SYSTEM = "system" + EVENT_TYPE_MANAGER = "manager" +) + type SEvent struct { Created time.Time `json:"Created"` - EventId string `json:"MessageID"` + EventId string `json:"Id"` Message string `json:"Message"` Severity string `json:"Severity"` + Type string `json:"type"` +} + +type SEventList []SEvent + +func (el SEventList) Len() int { return len(el) } +func (el SEventList) Swap(i, j int) { el[i], el[j] = el[j], el[i] } +func (el SEventList) Less(i, j int) bool { return el[i].Created.Before(el[j].Created) } + +func SortEvents(el []SEvent) { + sort.Sort(SEventList(el)) } type SBiosInfo struct { @@ -59,12 +83,48 @@ type SPower struct { } `json:"PowerMetrics"` } +func (p SPower) ToMetrics() []influxdb.SKeyValue { + return []influxdb.SKeyValue{ + { + Key: "PowerCapacityWatts", + Value: strconv.FormatInt(int64(p.PowerCapacityWatts), 10), + }, + { + Key: "PowerConsumedWatts", + Value: strconv.FormatInt(int64(p.PowerConsumedWatts), 10), + }, + { + Key: "AverageConsumedWatts", + Value: strconv.FormatInt(int64(p.PowerMetrics.AverageConsumedWatts), 10), + }, + { + Key: "IntervalInMin", + Value: strconv.FormatInt(int64(p.PowerMetrics.IntervalInMin), 10), + }, + { + Key: "MaxConsumedWatts", + Value: strconv.FormatInt(int64(p.PowerMetrics.MaxConsumedWatts), 10), + }, + { + Key: "MinConsumedWatts", + Value: strconv.FormatInt(int64(p.PowerMetrics.MinConsumedWatts), 10), + }, + } +} + type STemperature struct { - Name string `help:"Name"` + Name string `json:"Name"` PhysicalContext string `json:"PhysicalContext"` ReadingCelsius int `json:"ReadingCelsius"` } +func (t STemperature) ToMetric() influxdb.SKeyValue { + return influxdb.SKeyValue{ + Key: fmt.Sprintf("%s/%s", t.PhysicalContext, strings.ReplaceAll(t.Name, " ", "")), + Value: strconv.FormatInt(int64(t.ReadingCelsius), 10), + } +} + type SNTPConf struct { NTPServers []string `json:"NTPServers,allowempty"` ProtocolEnabled bool `json:"ProtocolEnabled,allowfalse"`