From 18e435418e4bb1e84a9dec0cbae44bd2ce1a8e43 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 24 Jul 2024 14:51:28 -0700 Subject: [PATCH 1/5] fix: govc: output Message field for 'EventEx' types EventEx will have an empty FullFormattedMessage field, as the event is not predefined in vCenter. --- govc/events/command.go | 20 +++++++++++++++----- simulator/event_manager.go | 5 +++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/govc/events/command.go b/govc/events/command.go index 73efcca87..12a1a6602 100644 --- a/govc/events/command.go +++ b/govc/events/command.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -117,11 +117,21 @@ func (cmd *events) printEvents(ctx context.Context, obj *types.ManagedObjectRefe r.Key = event.Key } - // if this is a TaskEvent gather a little more information - if t, ok := e.(*types.TaskEvent); ok { + switch x := e.(type) { + case *types.TaskEvent: // some tasks won't have this information, so just use the event message - if t.Info.Entity != nil { - r.Message = fmt.Sprintf("%s (target=%s %s)", r.Message, t.Info.Entity.Type, t.Info.EntityName) + if x.Info.Entity != nil { + r.Message = fmt.Sprintf("%s (target=%s %s)", r.Message, x.Info.Entity.Type, x.Info.EntityName) + } + case *types.EventEx: + if r.Message == "" { + r.Message = x.Message + } + if x.ObjectId != "" { + r.Message = fmt.Sprintf("%s (%s)", r.Message, x.ObjectId) + } + if cmd.Long { + r.Type = x.EventTypeId } } diff --git a/simulator/event_manager.go b/simulator/event_manager.go index 72404dbc2..1eed8214d 100644 --- a/simulator/event_manager.go +++ b/simulator/event_manager.go @@ -294,6 +294,11 @@ func (c *EventHistoryCollector) typeMatches(_ *Context, event types.BaseEvent, s } return false } + + if x, ok := event.(*types.EventEx); ok { + return matches(x.EventTypeId) + } + kind := reflect.ValueOf(event).Elem().Type() if matches(kind.Name()) { From 2975ade1b35710255234931dbaa90170b29c50f4 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 24 Jul 2024 14:54:32 -0700 Subject: [PATCH 2/5] govc: add event.post command This command can be used to trigger alarms and test use of EventEx in general. --- govc/events/post.go | 80 +++++++++++++++++++++++++++++++++++++++++++ govc/test/events.bats | 14 ++++++++ 2 files changed, 94 insertions(+) create mode 100644 govc/events/post.go diff --git a/govc/events/post.go b/govc/events/post.go new file mode 100644 index 000000000..cf73f87b8 --- /dev/null +++ b/govc/events/post.go @@ -0,0 +1,80 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package events + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/event" + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/types" +) + +type post struct { + *flags.DatacenterFlag + + types.EventEx +} + +func init() { + cli.Register("event.post", &post{}, true) +} + +func (cmd *post) Register(ctx context.Context, f *flag.FlagSet) { + cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) + cmd.DatacenterFlag.Register(ctx, f) + + f.StringVar(&cmd.EventTypeId, "i", "", "Event Type ID") + f.StringVar(&cmd.Message, "m", "", "Event message") + f.StringVar(&cmd.Severity, "s", string(types.EventEventSeverityInfo), "Event severity") +} + +func (cmd *post) Usage() string { + return "PATH" +} + +func (cmd *post) Description() string { + return `Post Event. + +Examples: + govc event.post -s warning -i com.vmware.wcp.RegisterVM.failure $vm + govc event.post -s info -i com.vmware.wcp.RegisterVM.success $vm + govc event.post -m "cluster degraded" /dc1/host/cluster1` +} + +func (cmd *post) Run(ctx context.Context, f *flag.FlagSet) error { + if f.NArg() != 1 { + return flag.ErrHelp + } + + c, err := cmd.Client() + if err != nil { + return err + } + + obj, err := cmd.ManagedObject(ctx, f.Arg(0)) + if err != nil { + return err + } + + cmd.ObjectType = obj.Type + cmd.ObjectId = obj.Value + + return event.NewManager(c).PostEvent(ctx, &cmd.EventEx) +} diff --git a/govc/test/events.bats b/govc/test/events.bats index cac4ee2c9..90ddcc1b0 100755 --- a/govc/test/events.bats +++ b/govc/test/events.bats @@ -93,3 +93,17 @@ load test_helper govc events 'vm/*' govc events -json 'vm/*' | jq . } + +@test "events post" { + vcsim_env + + export GOVC_SHOW_UNRELEASED=true + + run govc event.post -m testing123 + assert_failure + + run govc event.post -m testing123 /DC0 + assert_success + + govc events | grep testing123 +} From ea3e806f84597120047da4fff6937c7b944bea32 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 24 Jul 2024 15:04:39 -0700 Subject: [PATCH 3/5] api: add alarm package with helpers for AlarmManager and Alarms --- alarm/manager.go | 220 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 alarm/manager.go diff --git a/alarm/manager.go b/alarm/manager.go new file mode 100644 index 000000000..e3f0df5e2 --- /dev/null +++ b/alarm/manager.go @@ -0,0 +1,220 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alarm + +import ( + "context" + + "github.com/vmware/govmomi/event" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type Manager struct { + object.Common + + pc *property.Collector +} + +var Severity = map[types.ManagedEntityStatus]string{ + types.ManagedEntityStatusGray: "Unknown", + types.ManagedEntityStatusGreen: "Normal", + types.ManagedEntityStatusYellow: "Warning", + types.ManagedEntityStatusRed: "Alert", +} + +// GetManager wraps NewManager, returning ErrNotSupported +// when the client is not connected to a vCenter instance. +func GetManager(c *vim25.Client) (*Manager, error) { + if c.ServiceContent.AlarmManager == nil { + return nil, object.ErrNotSupported + } + return NewManager(c), nil +} + +func NewManager(c *vim25.Client) *Manager { + m := Manager{ + Common: object.NewCommon(c, *c.ServiceContent.AlarmManager), + pc: property.DefaultCollector(c), + } + + return &m +} + +func (m Manager) CreateAlarm(ctx context.Context, entity object.Reference, spec types.BaseAlarmSpec) (*types.ManagedObjectReference, error) { + req := types.CreateAlarm{ + This: m.Reference(), + Entity: entity.Reference(), + Spec: spec, + } + + res, err := methods.CreateAlarm(ctx, m.Client(), &req) + if err != nil { + return nil, err + } + return &res.Returnval, err +} + +func (m Manager) AcknowledgeAlarm(ctx context.Context, alarm types.ManagedObjectReference, entity object.Reference) error { + req := types.AcknowledgeAlarm{ + This: m.Reference(), + Alarm: alarm, + Entity: entity.Reference(), + } + + _, err := methods.AcknowledgeAlarm(ctx, m.Client(), &req) + + return err +} + +// GetAlarm returns available alarms defined on the entity. +func (m Manager) GetAlarm(ctx context.Context, entity object.Reference) ([]mo.Alarm, error) { + req := types.GetAlarm{ + This: m.Reference(), + } + + if entity != nil { + req.Entity = types.NewReference(entity.Reference()) + } + + res, err := methods.GetAlarm(ctx, m.Client(), &req) + if err != nil { + return nil, err + } + + if len(res.Returnval) == 0 { + return nil, nil + } + + alarms := make([]mo.Alarm, 0, len(res.Returnval)) + + err = m.pc.Retrieve(ctx, res.Returnval, []string{"info"}, &alarms) + if err != nil { + return nil, err + } + + return alarms, nil +} + +// StateInfo combines AlarmState with Alarm.Info +type StateInfo struct { + types.AlarmState + Info *types.AlarmInfo `json:"name,omitempty"` + Path string `json:"path,omitempty"` + Event types.BaseEvent `json:"event,omitempty"` +} + +// StateInfoOptions for the GetStateInfo method +type StateInfoOptions struct { + Declared bool + InventoryPath bool + Event bool +} + +// GetStateInfo combines AlarmState with Alarm.Info +func (m Manager) GetStateInfo(ctx context.Context, entity object.Reference, opts StateInfoOptions) ([]StateInfo, error) { + prop := "triggeredAlarmState" + if opts.Declared { + prop = "declaredAlarmState" + opts.Event = false + } + + var e mo.ManagedEntity + + err := m.pc.RetrieveOne(ctx, entity.Reference(), []string{prop}, &e) + if err != nil { + return nil, err + } + + var objs []types.ManagedObjectReference + alarms := append(e.DeclaredAlarmState, e.TriggeredAlarmState...) + if len(alarms) == 0 { + return nil, nil + } + for i := range alarms { + objs = append(objs, alarms[i].Alarm) + } + + var info []mo.Alarm + err = m.pc.Retrieve(ctx, objs, []string{"info"}, &info) + if err != nil { + return nil, err + } + + state := make([]StateInfo, len(alarms)) + paths := make(map[types.ManagedObjectReference]string) + + em := event.NewManager(m.Client()) + + for i, a := range alarms { + path := paths[a.Entity] + if opts.InventoryPath { + if path == "" { + path, err = find.InventoryPath(ctx, m.Client(), a.Entity) + if err != nil { + return nil, err + } + paths[a.Entity] = path + } + } else { + path = a.Entity.String() + } + + state[i] = StateInfo{ + AlarmState: a, + Path: path, + } + + for j := range info { + if info[j].Self == a.Alarm { + state[i].Info = &info[j].Info + break + } + } + + if !opts.Event || a.EventKey == 0 { + continue + } + + spec := types.EventFilterSpec{ + EventChainId: a.EventKey, + Entity: &types.EventFilterSpecByEntity{ + Entity: a.Entity, + Recursion: types.EventFilterSpecRecursionOptionSelf, + }, + } + + events, err := em.QueryEvents(ctx, spec) + if err != nil { + return nil, err + } + + for j := range events { + if events[j].GetEvent().Key == a.EventKey { + state[i].Event = events[j] + break + } + } + } + + return state, nil +} From d6a3b61498d18670a08fae983aa7c6cf566be98f Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 6 Aug 2024 09:25:57 -0700 Subject: [PATCH 4/5] govc: add alarm commands --- govc/USAGE.md | 56 +++++++++++ govc/alarm/create.go | 151 ++++++++++++++++++++++++++++++ govc/alarm/info.go | 119 +++++++++++++++++++++++ govc/alarm/rm.go | 65 +++++++++++++ govc/alarm/state.go | 197 +++++++++++++++++++++++++++++++++++++++ govc/flags/datacenter.go | 2 +- govc/main.go | 1 + 7 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 govc/alarm/create.go create mode 100644 govc/alarm/info.go create mode 100644 govc/alarm/rm.go create mode 100644 govc/alarm/state.go diff --git a/govc/USAGE.md b/govc/USAGE.md index c37f99cb4..8d18b6ed8 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -38,6 +38,8 @@ but appear via `govc $cmd -h`: - [about](#about) - [about.cert](#aboutcert) + - [alarm.info](#alarminfo) + - [alarms](#alarms) - [cluster.add](#clusteradd) - [cluster.change](#clusterchange) - [cluster.create](#clustercreate) @@ -455,6 +457,60 @@ Options: -thumbprint=false Output host hash and thumbprint only ``` +## alarm.info + +``` +Usage: govc alarm.info [OPTIONS] PATH + +Alarm definition info. + +Examples: + govc alarm.info + govc alarm.info /dc1/host/cluster1 + govc alarm.info -n alarm.WCPRegisterVMFailedAlarm + +Options: + -n=[] Alarm name +``` + +## alarms + +``` +Usage: govc alarms [OPTIONS] [PATH] + +Show triggered or declared alarms. + +Triggered alarms: alarms triggered by this entity or by its descendants. +Triggered alarms are propagated up the inventory hierarchy so that a user +can readily tell when a descendant has triggered an alarm. + +Declared alarms: alarms that apply to this managed entity. +Includes alarms defined on this entity and alarms inherited from the parent +entity, or from any ancestors in the inventory hierarchy. + +PATH defaults to the root folder '/'. +When PATH is provided it should be an absolute inventory path or relative +to GOVC_DATACENTER. See also: + govc find -h + govc tree -h + +Examples: + govc alarms + govc alarms vm/folder/vm-name + govc alarms /dc1/host/cluster1 + govc alarms /dc1/host/cluster1 -d + + govc alarms -n alarm.WCPRegisterVMFailedAlarm + govc alarms -ack /dc1/host/cluster1 + govc alarms -ack -n alarm.WCPRegisterVMFailedAlarm vm/vm-name + +Options: + -ack=false Acknowledge alarms + -d=false Show declared alarms + -l=false Long listing output + -n= Filter by alarm name +``` + ## cluster.add ``` diff --git a/govc/alarm/create.go b/govc/alarm/create.go new file mode 100644 index 000000000..b83dde434 --- /dev/null +++ b/govc/alarm/create.go @@ -0,0 +1,151 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alarm + +import ( + "context" + "flag" + "fmt" + + "github.com/vmware/govmomi/alarm" + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type create struct { + *flags.DatacenterFlag + + types.AlarmSpec + + r bool + kind string + + green, yellow, red string +} + +func init() { + cli.Register("alarm.create", &create{}, true) +} + +func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { + cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) + cmd.DatacenterFlag.Register(ctx, f) + + f.StringVar(&cmd.AlarmSpec.Name, "n", "", "Alarm name") + f.StringVar(&cmd.AlarmSpec.Description, "d", "", "Alarm description") + f.BoolVar(&cmd.Enabled, "enabled", true, "Enabled") + + f.StringVar(&cmd.kind, "type", "VirtualMachine", "Object type") + f.BoolVar(&cmd.r, "r", false, "Reconfigure existing alarm") + + f.StringVar(&cmd.green, "green", "", "green status event type") + f.StringVar(&cmd.yellow, "yellow", "", "yellow status event type") + f.StringVar(&cmd.red, "red", "", "red status event type") +} + +func (cmd *create) Usage() string { + return "[PATH]" +} + +func (cmd *create) Description() string { + return `Create alarm. + +Examples: + govc alarm.create -n "My Alarm" -green my.alarm.success -yellow my.alarm.failure + govc event.post -i my.alarm.failure $vm + govc alarms $vm` +} + +func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { + c, err := cmd.Client() + if err != nil { + return err + } + + obj := c.ServiceContent.RootFolder + if f.NArg() == 1 { + obj, err = cmd.ManagedObject(ctx, f.Arg(0)) + if err != nil { + return err + } + } + + var or types.OrAlarmExpression + + expressions := []struct { + status types.ManagedEntityStatus + typeID string + }{ + {types.ManagedEntityStatusGreen, cmd.green}, + {types.ManagedEntityStatusYellow, cmd.yellow}, + {types.ManagedEntityStatusRed, cmd.red}, + } + + for _, exp := range expressions { + if exp.typeID != "" { + or.Expression = append(or.Expression, &types.EventAlarmExpression{ + EventType: "vim.event.EventEx", + EventTypeId: exp.typeID, + ObjectType: "vim." + cmd.kind, + Status: exp.status, + }) + } + } + + cmd.AlarmSpec.Expression = &or + + m, err := alarm.GetManager(c) + if err != nil { + return err + } + + if cmd.r { + alarms, err := m.GetAlarm(ctx, obj) + if err != nil { + return err + } + + var alarm *mo.Alarm + for i := range alarms { + if alarms[i].Info.Name == cmd.AlarmSpec.Name { + alarm = &alarms[i] + break + } + } + if alarm == nil { + return fmt.Errorf("%s not found", cmd.AlarmSpec.Name) + } + + _, err = methods.ReconfigureAlarm(ctx, c, &types.ReconfigureAlarm{ + This: alarm.Self, + Spec: &cmd.AlarmSpec, + }) + return err + } + + ref, err := m.CreateAlarm(ctx, obj, &cmd.AlarmSpec) + if err != nil { + return err + } + + fmt.Println(ref.Value) + + return nil +} diff --git a/govc/alarm/info.go b/govc/alarm/info.go new file mode 100644 index 000000000..f8447ab17 --- /dev/null +++ b/govc/alarm/info.go @@ -0,0 +1,119 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alarm + +import ( + "context" + "flag" + "fmt" + "io" + "text/tabwriter" + + "github.com/vmware/govmomi/alarm" + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/mo" +) + +type info struct { + *flags.DatacenterFlag + + name flags.StringList +} + +func init() { + cli.Register("alarm.info", &info{}) +} + +func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) { + cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) + cmd.DatacenterFlag.Register(ctx, f) + + f.Var(&cmd.name, "n", "Alarm name") +} + +func (cmd *info) Usage() string { + return "PATH" +} + +func (cmd *info) Description() string { + return `Alarm definition info. + +Examples: + govc alarm.info + govc alarm.info /dc1/host/cluster1 + govc alarm.info -n alarm.WCPRegisterVMFailedAlarm` +} + +type infoResult []mo.Alarm + +func (r infoResult) Dump() any { + return []mo.Alarm(r) +} + +func (r infoResult) Write(w io.Writer) error { + tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0) + for _, a := range r { + fmt.Fprintf(tw, "Name:\t%s\n", a.Info.Name) + fmt.Fprintf(tw, " SystemName:\t%s\n", a.Info.SystemName) + fmt.Fprintf(tw, " Description:\t%s\n", a.Info.Description) + fmt.Fprintf(tw, " Enabled:\t%t\n", a.Info.Enabled) + } + return tw.Flush() +} + +func (cmd *info) findAlarm(alarms []mo.Alarm) []mo.Alarm { + if len(cmd.name) == 0 { + return alarms + } + var match []mo.Alarm + for _, alarm := range alarms { + for _, name := range cmd.name { + if alarm.Info.SystemName == name || alarm.Info.Name == name { + match = append(match, alarm) + } + } + } + return match +} + +func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error { + c, err := cmd.Client() + if err != nil { + return err + } + + obj := c.ServiceContent.RootFolder + if f.NArg() == 1 { + obj, err = cmd.ManagedObject(ctx, f.Arg(0)) + if err != nil { + return err + } + } + + m, err := alarm.GetManager(c) + if err != nil { + return err + } + + alarms, err := m.GetAlarm(ctx, obj) + if err != nil { + return err + } + + return cmd.WriteResult(infoResult(cmd.findAlarm(alarms))) +} diff --git a/govc/alarm/rm.go b/govc/alarm/rm.go new file mode 100644 index 000000000..652f16998 --- /dev/null +++ b/govc/alarm/rm.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alarm + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/types" +) + +type rm struct { + *flags.ClientFlag +} + +func init() { + cli.Register("alarm.rm", &rm{}, true) +} + +func (cmd *rm) Register(ctx context.Context, f *flag.FlagSet) { + cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) + cmd.ClientFlag.Register(ctx, f) +} + +func (cmd *rm) Usage() string { + return "ID" +} + +func (cmd *rm) Run(ctx context.Context, f *flag.FlagSet) error { + c, err := cmd.Client() + if err != nil { + return err + } + + id := f.Arg(0) + var ref types.ManagedObjectReference + + if !ref.FromString(id) { + ref.Type = "Alarm" + ref.Value = id + } + + _, err = methods.RemoveAlarm(ctx, c, &types.RemoveAlarm{ + This: ref, + }) + + return err +} diff --git a/govc/alarm/state.go b/govc/alarm/state.go new file mode 100644 index 000000000..04b821cc2 --- /dev/null +++ b/govc/alarm/state.go @@ -0,0 +1,197 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alarm + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io" + "text/tabwriter" + "time" + + "github.com/vmware/govmomi/alarm" + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/types" +) + +type state struct { + *flags.DatacenterFlag + *flags.OutputFlag + + ack bool + name string + alarm.StateInfoOptions +} + +func init() { + cli.Register("alarms", &state{}) +} + +func (cmd *state) Register(ctx context.Context, f *flag.FlagSet) { + cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) + cmd.DatacenterFlag.Register(ctx, f) + + cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) + cmd.OutputFlag.Register(ctx, f) + + f.BoolVar(&cmd.ack, "ack", false, "Acknowledge alarms") + f.StringVar(&cmd.name, "n", "", "Filter by alarm name") + f.BoolVar(&cmd.Declared, "d", false, "Show declared alarms") + f.BoolVar(&cmd.InventoryPath, "l", false, "Long listing output") +} + +func (cmd *state) Usage() string { + return "[PATH]" +} + +func (cmd *state) Description() string { + return `Show triggered or declared alarms. + +Triggered alarms: alarms triggered by this entity or by its descendants. +Triggered alarms are propagated up the inventory hierarchy so that a user +can readily tell when a descendant has triggered an alarm. + +Declared alarms: alarms that apply to this managed entity. +Includes alarms defined on this entity and alarms inherited from the parent +entity, or from any ancestors in the inventory hierarchy. + +PATH defaults to the root folder '/'. +When PATH is provided it should be an absolute inventory path or relative +to GOVC_DATACENTER. See also: + govc find -h + govc tree -h + +Examples: + govc alarms + govc alarms vm/folder/vm-name + govc alarms /dc1/host/cluster1 + govc alarms /dc1/host/cluster1 -d + + govc alarms -n alarm.WCPRegisterVMFailedAlarm + govc alarms -ack /dc1/host/cluster1 + govc alarms -ack -n alarm.WCPRegisterVMFailedAlarm vm/vm-name` +} + +func (cmd *state) Process(ctx context.Context) error { + if err := cmd.DatacenterFlag.Process(ctx); err != nil { + return err + } + return cmd.OutputFlag.Process(ctx) +} + +func (cmd *state) filterAlarms(ctx context.Context, m *alarm.Manager, obj types.ManagedObjectReference) ([]alarm.StateInfo, error) { + alarms, err := m.GetStateInfo(ctx, obj, cmd.StateInfoOptions) + if err != nil || cmd.name == "" { + return alarms, err + } + + var match []alarm.StateInfo + for _, alarm := range alarms { + if alarm.Info.SystemName == cmd.name || alarm.Info.Name == cmd.name { + match = append(match, alarm) + } + } + + return match, nil +} + +type stateResult struct { + info []alarm.StateInfo + cmd *state +} + +func (r *stateResult) Dump() any { + return r.info +} + +func (r *stateResult) MarshalJSON() ([]byte, error) { + return json.Marshal(r.info) +} + +func (r *stateResult) Write(w io.Writer) error { + tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0) + + fmt.Fprintf(tw, "Alarm Name\tObject\tSeverity\tTriggered Time\tAcknowledged Time\tAcknowledged By\n") + + for _, a := range r.info { + name := a.Info.Name + tt := a.Time.Format(time.Stamp) + at := "" + by := a.AcknowledgedByUser + if a.AcknowledgedTime != nil { + at = a.AcknowledgedTime.Format(time.Stamp) + } + fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", name, a.Path, alarm.Severity[a.OverallStatus], tt, at, by) + } + + return tw.Flush() +} + +func (cmd *state) Run(ctx context.Context, f *flag.FlagSet) error { + if f.NArg() > 1 { + return flag.ErrHelp + } + if cmd.ack && cmd.Declared { + return flag.ErrHelp + } + + c, err := cmd.Client() + if err != nil { + return err + } + + obj := c.ServiceContent.RootFolder + if f.NArg() == 1 { + obj, err = cmd.ManagedObject(ctx, f.Arg(0)) + if err != nil { + return err + } + } + + cmd.StateInfoOptions.Event = cmd.All() + + m, err := alarm.GetManager(c) + if err != nil { + return err + } + + alarms, err := cmd.filterAlarms(ctx, m, obj) + if err != nil { + return err + } + + if cmd.ack { + for _, alarm := range alarms { + if alarm.Acknowledged != nil && *alarm.Acknowledged { + continue + } + if err := m.AcknowledgeAlarm(ctx, alarm.Alarm, alarm.Entity); err != nil { + return err + } + } + + alarms, err = cmd.filterAlarms(ctx, m, obj) + if err != nil { + return err + } + } + + return cmd.WriteResult(&stateResult{alarms, cmd}) +} diff --git a/govc/flags/datacenter.go b/govc/flags/datacenter.go index d73f36a81..7cfcef3bd 100644 --- a/govc/flags/datacenter.go +++ b/govc/flags/datacenter.go @@ -174,7 +174,7 @@ func (flag *DatacenterFlag) ManagedObject(ctx context.Context, arg string) (type for _, o := range l { objs = append(objs, o.Object.Reference()) } - return ref, fmt.Errorf("%d objects at path %q: %s", len(l), arg, objs) + return ref, fmt.Errorf("%d objects match %q: %s (unique inventory path required)", len(l), arg, objs) } } diff --git a/govc/main.go b/govc/main.go index bae197e7b..5fb8de568 100644 --- a/govc/main.go +++ b/govc/main.go @@ -20,6 +20,7 @@ import ( "os" _ "github.com/vmware/govmomi/govc/about" + _ "github.com/vmware/govmomi/govc/alarm" "github.com/vmware/govmomi/govc/cli" _ "github.com/vmware/govmomi/govc/cluster" _ "github.com/vmware/govmomi/govc/cluster/draft" From 9c28210856cfa5564000411c1e63f5d62a955de6 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 6 Aug 2024 19:25:14 -0700 Subject: [PATCH 5/5] vcsim: add AlarmManager Closes #3489 Closes #2476 --- govc/object/save.go | 21 +++ govc/test/alarms.bats | 127 ++++++++++++++++ simulator/alarm_manager.go | 270 +++++++++++++++++++++++++++++++++ simulator/event_manager.go | 21 +++ simulator/model.go | 1 + simulator/registry.go | 9 ++ simulator/vpx/alarm_manager.go | 219 ++++++++++++++++++++++++++ 7 files changed, 668 insertions(+) create mode 100755 govc/test/alarms.bats create mode 100644 simulator/alarm_manager.go create mode 100644 simulator/vpx/alarm_manager.go diff --git a/govc/object/save.go b/govc/object/save.go index b6f40358f..a2d52a22e 100644 --- a/govc/object/save.go +++ b/govc/object/save.go @@ -155,12 +155,26 @@ func saveHostSystem(ctx context.Context, c *vim25.Client, ref types.ManagedObjec return []saveMethod{{"QueryTpmAttestationReport", res}}, nil } +func saveAlarmManager(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) { + res, err := methods.GetAlarm(ctx, c, &types.GetAlarm{This: ref}) + if err != nil { + return nil, err + } + pc := property.DefaultCollector(c) + var content []types.ObjectContent + if err = pc.Retrieve(ctx, res.Returnval, nil, &content); err != nil { + return nil, err + } + return []saveMethod{{"GetAlarm", res}, {"", content}}, nil +} + // saveObjects maps object types to functions that can save data that isn't available via the PropertyCollector var saveObjects = map[string]func(context.Context, *vim25.Client, types.ManagedObjectReference) ([]saveMethod, error){ "VmwareDistributedVirtualSwitch": saveDVS, "EnvironmentBrowser": saveEnvironmentBrowser, "HostNetworkSystem": saveHostNetworkSystem, "HostSystem": saveHostSystem, + "AlarmManager": saveAlarmManager, } func isNotConnected(err error) bool { @@ -204,6 +218,13 @@ func (cmd *save) save(content []types.ObjectContent) error { return err } for _, obj := range objs { + if obj.Name == "" { + err = cmd.save(obj.Data.([]types.ObjectContent)) + if err != nil { + return err + } + continue + } err = cmd.write(filepath.Join(ref, obj.Name), obj.Data) if err != nil { return err diff --git a/govc/test/alarms.bats b/govc/test/alarms.bats new file mode 100755 index 000000000..a71966693 --- /dev/null +++ b/govc/test/alarms.bats @@ -0,0 +1,127 @@ +#!/usr/bin/env bats + +load test_helper + +@test "alarms" { + vcsim_env + + run govc alarms + assert_success + + run govc alarms -d + assert_success + + vm=/DC0/vm/DC0_H0_VM0 + + run govc alarms $vm + assert_success + + run govc object.collect -s $vm triggeredAlarmState + assert_success "" # empty + + run env GOVC_SHOW_UNRELEASED=true govc event.post -s info -i vcsim.vm.success $vm + assert_success + + run govc alarms $vm + assert_success + [ ${#lines[@]} -eq 1 ] # header only, no alarm triggered + + run env GOVC_SHOW_UNRELEASED=true govc event.post -s warning -i vcsim.vm.failure $vm + assert_success + + run govc alarms $vm + assert_success + assert_matches "Warning" + + run govc alarms -l $vm + assert_success + + run govc alarms -json $vm + assert_success + alarms="$output" + run jq -r .[].event.eventTypeId <<<"$alarms" + assert_success "vcsim.vm.failure" + + run govc object.collect -json -s $vm triggeredAlarmState + assert_success + state="$output" + run jq -r .[].overallStatus <<<"$state" + assert_success "yellow" + run jq -r .[].acknowledged <<<"$state" + assert_success "false" + + run govc alarms -ack + assert_success + + run govc object.collect -json -s $vm triggeredAlarmState + assert_success + state="$output" + run jq -r .[].overallStatus <<<"$state" + assert_success "yellow" + run jq -r .[].acknowledged <<<"$state" + assert_success "true" + + run env GOVC_SHOW_UNRELEASED=true govc event.post -s info -i vcsim.vm.success $vm + assert_success + + run govc object.collect -s $vm triggeredAlarmState + assert_success "" # empty + + run govc object.collect -s / triggeredAlarmState + assert_success "" # empty +} + +@test "alarm.info" { + vcsim_env + + run govc alarm.info + assert_success + + run govc alarm.info -n alarm.VmErrorAlarm + assert_success + assert_matches "Virtual machine error" + + run govc alarm.info -n invalid + assert_success "" +} + +@test "alarm -esx" { + vcsim_env -esx + + run govc object.collect -s - content.alarmManager + assert_success "" # empty, no AlarmManager + + run govc alarm.info + assert_failure # not supported +} + +@test "alarm.create" { + vcsim_env + + export GOVC_SHOW_UNRELEASED=true + + run govc alarm.create -n "My Alarm" -green my.alarm.success -yellow my.alarm.failure + assert_success + id="$output" + + self=$(govc alarm.info -json -n "My Alarm" | jq -r .[].self.value) + assert_equal "$id" "$self" + + run govc alarm.info -n "My Alarm" + assert_success + + run govc alarm.create -n "My Alarm" + assert_failure # DuplicateName + + run govc alarm.create -n "My Alarm" -r -d "This is my alarm description" + assert_success + + run govc alarm.rm "$id" + assert_success + + run govc alarm.rm "$id" + assert_failure + + run govc alarm.info -n "My Alarm" + assert_success "" +} diff --git a/simulator/alarm_manager.go b/simulator/alarm_manager.go new file mode 100644 index 000000000..91c94ff80 --- /dev/null +++ b/simulator/alarm_manager.go @@ -0,0 +1,270 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package simulator + +import ( + "strings" + "time" + + "github.com/vmware/govmomi/simulator/vpx" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" + "github.com/vmware/govmomi/vim25/types" +) + +type AlarmManager struct { + mo.AlarmManager + + types.GetAlarmResponse +} + +func (m *AlarmManager) init(r *Registry) { + if m.GetAlarmResponse.Returnval != nil { + return + } + + m.GetAlarmResponse.Returnval = make([]types.ManagedObjectReference, len(vpx.Alarm)) + for i, alarm := range vpx.Alarm { + m.GetAlarmResponse.Returnval[i] = alarm.Self + r.Put(&Alarm{Alarm: alarm}) + } +} + +func (*AlarmManager) trimPrefix(s string) string { + return strings.TrimPrefix(s, "vim.") +} + +func (*AlarmManager) key(refs ...types.ManagedObjectReference) string { + keys := make([]string, len(refs)) + for i := range refs { + s := strings.Split(refs[i].Value, "-") + keys[i] = s[len(s)-1] + } + return strings.Join(keys, ".") +} + +// only handling the common use case of EventEx for now +func (m *AlarmManager) matchAlarm(alarm *Alarm, event *types.EventEx) (*mo.Alarm, types.ManagedEntityStatus) { + id := event.EventTypeId + kind := m.trimPrefix(event.ObjectType) + + switch op := alarm.Info.Expression.(type) { + case *types.OrAlarmExpression: + for i := range op.Expression { + switch x := op.Expression[i].(type) { + case *types.EventAlarmExpression: + if x.EventTypeId == id && kind == m.trimPrefix(x.ObjectType) { + return &alarm.Alarm, x.Status + } + } + } + } + return nil, "" +} + +// update (e.g. triggeredAlarmState) and propagate up the inventory hierarchy +func (*AlarmManager) update(ctx *Context, me mo.Entity, update func(mo.Entity) *types.ManagedObjectReference) { + for { + if me == nil { + break + } + ctx.WithLock(me, func() { + parent := update(me) + if parent == nil { + me = nil + } else { + me = ctx.Map.Get(*parent).(mo.Entity) + } + }) + } +} + +// postEvent triggers Alarms based on Events +func (m *AlarmManager) postEvent(ctx *Context, base types.BaseEvent) { + event, ok := base.(*types.EventEx) + if !ok { + return + } + + entity := types.ManagedObjectReference{Type: event.ObjectType, Value: event.ObjectId} + me := ctx.Map.Get(entity).(mo.Entity) + + for _, ref := range m.GetAlarmResponse.Returnval { + alarm := ctx.Map.Get(ref).(*Alarm) + match, status := m.matchAlarm(alarm, event) + if match == nil { + continue + } + + now := time.Now() + key := m.key(match.Self, entity) + + update := func(me mo.Entity) *types.ManagedObjectReference { + obj := me.Entity() + + for i, state := range obj.TriggeredAlarmState { + if state.Key != key { + continue + } + + switch status { + case state.OverallStatus: + // no change + return nil + case types.ManagedEntityStatusGreen: + // remove + obj.TriggeredAlarmState = + append(obj.TriggeredAlarmState[:i], + obj.TriggeredAlarmState[i+1:]...) + return obj.Parent + default: + // status change (e.g. yellow -> red) + obj.TriggeredAlarmState[i].OverallStatus = status + return obj.Parent + } + } + + if status == types.ManagedEntityStatusGreen { + return nil // green only clears a triggered alarm + } + + // add + state := types.AlarmState{ + Key: key, + Entity: entity, + Alarm: match.Self, + OverallStatus: status, + Time: now, + EventKey: event.Key, + Acknowledged: types.NewBool(false), + } + + obj.TriggeredAlarmState = append(obj.TriggeredAlarmState, state) + + return obj.Parent + } + + m.update(ctx, me, update) + } +} + +func (m *AlarmManager) GetAlarm(ctx *Context, req *types.GetAlarm) soap.HasFault { + body := &methods.GetAlarmBody{ + Res: new(types.GetAlarmResponse), + } + + if req.Entity == nil || *req.Entity == ctx.Map.content().RootFolder { + body.Res.Returnval = m.GetAlarmResponse.Returnval + } // else TODO + + return body +} + +func (m *AlarmManager) CreateAlarm(ctx *Context, req *types.CreateAlarm) soap.HasFault { + body := new(methods.CreateAlarmBody) + + name := req.Spec.GetAlarmSpec().Name + + for _, alarm := range ctx.Map.AllReference("Alarm") { + if alarm.(*Alarm).Info.Name == name { + body.Fault_ = Fault("", &types.DuplicateName{Name: name}) + return body + } + } + + alarm := Alarm{ + Alarm: mo.Alarm{ + Info: types.AlarmInfo{ + AlarmSpec: *req.Spec.GetAlarmSpec(), + Entity: req.Entity, + LastModifiedTime: time.Now(), + LastModifiedUser: ctx.Session.UserName, + }, + }, + } + + ref := ctx.Map.Put(&alarm).Reference() + alarm.Info.Alarm = ref + m.GetAlarmResponse.Returnval = append(m.GetAlarmResponse.Returnval, ref) + + body.Res = &types.CreateAlarmResponse{ + Returnval: ref, + } + + return body +} + +func (m *AlarmManager) AcknowledgeAlarm(ctx *Context, req *types.AcknowledgeAlarm) soap.HasFault { + body := new(methods.AcknowledgeAlarmBody) + + now := types.NewTime(time.Now()) + key := m.key(req.Alarm, req.Entity) + me := ctx.Map.Get(req.Entity).(mo.Entity) + + update := func(me mo.Entity) *types.ManagedObjectReference { + obj := me.Entity() + + for i, state := range obj.TriggeredAlarmState { + if state.Key == key { + if *obj.TriggeredAlarmState[i].Acknowledged { + return nil // already ack-ed + } + obj.TriggeredAlarmState[i].Acknowledged = types.NewBool(true) + obj.TriggeredAlarmState[i].AcknowledgedTime = now + obj.TriggeredAlarmState[i].AcknowledgedByUser = ctx.Session.UserName + return obj.Parent + } + } + + return nil + } + + m.update(ctx, me, update) + + body.Res = new(types.AcknowledgeAlarmResponse) + + return body +} + +type Alarm struct { + mo.Alarm +} + +func (a *Alarm) ReconfigureAlarm(ctx *Context, req *types.ReconfigureAlarm) soap.HasFault { + body := new(methods.ReconfigureAlarmBody) + + // TODO: spec validation + + a.Info.AlarmSpec = *req.Spec.GetAlarmSpec() + + body.Res = new(types.ReconfigureAlarmResponse) + + return body +} + +func (a *Alarm) RemoveAlarm(ctx *Context, req *types.RemoveAlarm) soap.HasFault { + m := ctx.Map.AlarmManager() + + RemoveReference(&m.GetAlarmResponse.Returnval, req.This) + + ctx.Map.Remove(ctx, req.This) + + return &methods.RemoveAlarmBody{ + Res: new(types.RemoveAlarmResponse), + } +} diff --git a/simulator/event_manager.go b/simulator/event_manager.go index 1eed8214d..94edea51f 100644 --- a/simulator/event_manager.go +++ b/simulator/event_manager.go @@ -166,6 +166,10 @@ func (m *EventManager) PostEvent(ctx *Context, req *types.PostEvent) soap.HasFau }) } + if m := ctx.Map.AlarmManager(); m != nil { + ctx.WithLock(m, func() { m.postEvent(ctx, req.EventToPost) }) + } + return &methods.PostEventBody{ Res: new(types.PostEventResponse), } @@ -229,6 +233,11 @@ func doEntityEventArgument(event types.BaseEvent, f func(types.ManagedObjectRefe // eventFilterSelf returns true if self is one of the entity arguments in the event. func eventFilterSelf(event types.BaseEvent, self types.ManagedObjectReference) bool { + if x, ok := event.(*types.EventEx); ok { + if self.Type == x.ObjectType && self.Value == x.ObjectId { + return true + } + } return doEntityEventArgument(event, func(ref types.ManagedObjectReference, _ *types.EntityEventArgument) bool { return self == ref }) @@ -280,6 +289,17 @@ func (c *EventHistoryCollector) entityMatches(ctx *Context, event types.BaseEven return false } +// chainMatches returns true if spec.EventChainId matches the event. +func (c *EventHistoryCollector) chainMatches(_ *Context, event types.BaseEvent, spec *types.EventFilterSpec) bool { + e := event.GetEvent() + if spec.EventChainId != 0 { + if e.ChainId != spec.EventChainId { + return false + } + } + return true +} + // typeMatches returns true if one of the spec EventTypeId types matches the event. func (c *EventHistoryCollector) typeMatches(_ *Context, event types.BaseEvent, spec *types.EventFilterSpec) bool { if len(spec.EventTypeId) == 0 { @@ -339,6 +359,7 @@ func (c *EventHistoryCollector) eventMatches(ctx *Context, event types.BaseEvent spec := c.Filter.(types.EventFilterSpec) matchers := []func(*Context, types.BaseEvent, *types.EventFilterSpec) bool{ + c.chainMatches, c.typeMatches, c.timeMatches, c.entityMatches, diff --git a/simulator/model.go b/simulator/model.go index aa91604bf..a595f1897 100644 --- a/simulator/model.go +++ b/simulator/model.go @@ -232,6 +232,7 @@ func (*Model) fmtName(prefix string, num int) string { // kinds maps managed object types to their vcsim wrapper types var kinds = map[string]reflect.Type{ + "AlarmManager": reflect.TypeOf((*AlarmManager)(nil)).Elem(), "AuthorizationManager": reflect.TypeOf((*AuthorizationManager)(nil)).Elem(), "ClusterComputeResource": reflect.TypeOf((*ClusterComputeResource)(nil)).Elem(), "CustomFieldsManager": reflect.TypeOf((*CustomFieldsManager)(nil)).Elem(), diff --git a/simulator/registry.go b/simulator/registry.go index acfc2265a..eebfec907 100644 --- a/simulator/registry.go +++ b/simulator/registry.go @@ -516,6 +516,15 @@ func (r *Registry) SearchIndex() *SearchIndex { return r.Get(r.content().SearchIndex.Reference()).(*SearchIndex) } +// AlarmManager returns the AlarmManager singleton +func (r *Registry) AlarmManager() *AlarmManager { + ref := r.content().AlarmManager + if ref == nil { + return nil // ESX + } + return r.Get(*ref).(*AlarmManager) +} + // EventManager returns the EventManager singleton func (r *Registry) EventManager() *EventManager { return r.Get(r.content().EventManager.Reference()).(*EventManager) diff --git a/simulator/vpx/alarm_manager.go b/simulator/vpx/alarm_manager.go new file mode 100644 index 000000000..c649168b6 --- /dev/null +++ b/simulator/vpx/alarm_manager.go @@ -0,0 +1,219 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vpx + +import ( + "time" + + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +// Alarm captured using: +// govc alarm.info -dump -n alarm.VmErrorAlarm -n alarm.HostErrorAlarm +var Alarm = []mo.Alarm{ + { + ExtensibleManagedObject: mo.ExtensibleManagedObject{ + Self: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-384", ServerGUID: ""}, + Value: nil, + AvailableField: nil, + }, + Info: types.AlarmInfo{ + AlarmSpec: types.AlarmSpec{ + Name: "vcsim VM Alarm", + SystemName: "", + Description: "vcsim alarm for Virtual Machines", + Enabled: true, + Expression: &types.OrAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Expression: []types.BaseAlarmExpression{ + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "EventEx", + EventTypeId: "vcsim.vm.success", + ObjectType: "VirtualMachine", + Status: "green", + }, + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "EventEx", + EventTypeId: "vcsim.vm.failure", + ObjectType: "VirtualMachine", + Status: "yellow", + }, + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "EventEx", + EventTypeId: "vcsim.vm.fatal", + ObjectType: "VirtualMachine", + Status: "red", + }, + }, + }, + Action: nil, + ActionFrequency: 0, + Setting: &types.AlarmSetting{ + ToleranceRange: 0, + ReportingFrequency: 300, + }, + }, + Key: "", + Alarm: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-384", ServerGUID: ""}, + Entity: types.ManagedObjectReference{Type: "Folder", Value: "group-d1", ServerGUID: ""}, + LastModifiedTime: time.Now(), + LastModifiedUser: "VSPHERE.LOCAL\\Administrator", + CreationEventId: 0, + }, + }, + { + ExtensibleManagedObject: mo.ExtensibleManagedObject{ + Self: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-11", ServerGUID: ""}, + Value: nil, + AvailableField: nil, + }, + Info: types.AlarmInfo{ + AlarmSpec: types.AlarmSpec{ + Name: "Host error", + SystemName: "alarm.HostErrorAlarm", + Description: "Default alarm to monitor host error and warning events", + Enabled: true, + Expression: &types.OrAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Expression: []types.BaseAlarmExpression{ + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "GeneralHostErrorEvent", + EventTypeId: "", + ObjectType: "HostSystem", + Status: "", + }, + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "GeneralHostWarningEvent", + EventTypeId: "", + ObjectType: "HostSystem", + Status: "", + }, + }, + }, + Action: &types.GroupAlarmAction{ + AlarmAction: types.AlarmAction{}, + Action: []types.BaseAlarmAction{ + &types.AlarmTriggeringAction{ + AlarmAction: types.AlarmAction{}, + Action: &types.SendSNMPAction{}, + TransitionSpecs: []types.AlarmTriggeringActionTransitionSpec{ + { + StartState: "yellow", + FinalState: "red", + Repeats: true, + }, + }, + Green2yellow: false, + Yellow2red: false, + Red2yellow: false, + Yellow2green: false, + }, + }, + }, + ActionFrequency: 0, + Setting: &types.AlarmSetting{ + ToleranceRange: 0, + ReportingFrequency: 300, + }, + }, + Key: "", + Alarm: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-11", ServerGUID: ""}, + Entity: types.ManagedObjectReference{Type: "Folder", Value: "group-d1", ServerGUID: ""}, + LastModifiedTime: time.Now(), + LastModifiedUser: "", + CreationEventId: 0, + }, + }, + { + ExtensibleManagedObject: mo.ExtensibleManagedObject{ + Self: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-12", ServerGUID: ""}, + Value: nil, + AvailableField: nil, + }, + Info: types.AlarmInfo{ + AlarmSpec: types.AlarmSpec{ + Name: "Virtual machine error", + SystemName: "alarm.VmErrorAlarm", + Description: "Default alarm to monitor virtual machine error and warning events", + Enabled: true, + Expression: &types.OrAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Expression: []types.BaseAlarmExpression{ + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "GeneralVmErrorEvent", + EventTypeId: "", + ObjectType: "VirtualMachine", + Status: "", + }, + &types.EventAlarmExpression{ + AlarmExpression: types.AlarmExpression{}, + Comparisons: nil, + EventType: "GeneralVmWarningEvent", + EventTypeId: "", + ObjectType: "VirtualMachine", + Status: "", + }, + }, + }, + Action: &types.GroupAlarmAction{ + AlarmAction: types.AlarmAction{}, + Action: []types.BaseAlarmAction{ + &types.AlarmTriggeringAction{ + AlarmAction: types.AlarmAction{}, + Action: &types.SendSNMPAction{}, + TransitionSpecs: []types.AlarmTriggeringActionTransitionSpec{ + { + StartState: "yellow", + FinalState: "red", + Repeats: true, + }, + }, + Green2yellow: false, + Yellow2red: false, + Red2yellow: false, + Yellow2green: false, + }, + }, + }, + ActionFrequency: 0, + Setting: &types.AlarmSetting{ + ToleranceRange: 0, + ReportingFrequency: 300, + }, + }, + Key: "", + Alarm: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-12", ServerGUID: ""}, + Entity: types.ManagedObjectReference{Type: "Folder", Value: "group-d1", ServerGUID: ""}, + LastModifiedTime: time.Now(), + LastModifiedUser: "", + CreationEventId: 0, + }, + }, +}