diff --git a/api/specs/specs.pb.go b/api/specs/specs.pb.go index 17b68cc..a28242e 100644 --- a/api/specs/specs.pb.go +++ b/api/specs/specs.pb.go @@ -261,6 +261,51 @@ func (x *MachineStatusSpec) GetLastRebootTimestamp() *timestamppb.Timestamp { return nil } +type PowerStatusSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PowerState PowerState `protobuf:"varint,1,opt,name=power_state,json=powerState,proto3,enum=baremetalproviderspecs.PowerState" json:"power_state,omitempty"` +} + +func (x *PowerStatusSpec) Reset() { + *x = PowerStatusSpec{} + mi := &file_specs_specs_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PowerStatusSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PowerStatusSpec) ProtoMessage() {} + +func (x *PowerStatusSpec) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PowerStatusSpec.ProtoReflect.Descriptor instead. +func (*PowerStatusSpec) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{2} +} + +func (x *PowerStatusSpec) GetPowerState() PowerState { + if x != nil { + return x.PowerState + } + return PowerState_POWER_STATE_UNKNOWN +} + type PowerManagement_IPMI struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -274,7 +319,7 @@ type PowerManagement_IPMI struct { func (x *PowerManagement_IPMI) Reset() { *x = PowerManagement_IPMI{} - mi := &file_specs_specs_proto_msgTypes[2] + mi := &file_specs_specs_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -286,7 +331,7 @@ func (x *PowerManagement_IPMI) String() string { func (*PowerManagement_IPMI) ProtoMessage() {} func (x *PowerManagement_IPMI) ProtoReflect() protoreflect.Message { - mi := &file_specs_specs_proto_msgTypes[2] + mi := &file_specs_specs_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -340,7 +385,7 @@ type PowerManagement_API struct { func (x *PowerManagement_API) Reset() { *x = PowerManagement_API{} - mi := &file_specs_specs_proto_msgTypes[3] + mi := &file_specs_specs_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -352,7 +397,7 @@ func (x *PowerManagement_API) String() string { func (*PowerManagement_API) ProtoMessage() {} func (x *PowerManagement_API) ProtoReflect() protoreflect.Message { - mi := &file_specs_specs_proto_msgTypes[3] + mi := &file_specs_specs_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -423,23 +468,29 @@ var file_specs_specs_proto_rawDesc = []byte{ 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x2a, 0x6d, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, - 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, - 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x58, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, - 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x5f, - 0x50, 0x58, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, - 0x44, 0x45, 0x5f, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x10, 0x03, 0x2a, - 0x4e, 0x0a, 0x0a, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, - 0x13, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, - 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x46, 0x46, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, - 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x4e, 0x10, 0x02, 0x42, - 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, - 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2d, 0x69, 0x6e, - 0x66, 0x72, 0x61, 0x2d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x61, 0x72, - 0x65, 0x2d, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x22, 0x56, 0x0a, 0x0f, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, + 0x70, 0x65, 0x63, 0x12, 0x43, 0x0a, 0x0b, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, + 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, + 0x73, 0x2e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x70, 0x6f, + 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x6d, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x74, + 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, + 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x42, + 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x50, + 0x58, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, + 0x45, 0x5f, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x5f, 0x50, 0x58, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, + 0x14, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x41, 0x4c, 0x4f, 0x53, + 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x10, 0x03, 0x2a, 0x4e, 0x0a, 0x0a, 0x50, 0x6f, 0x77, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, + 0x0a, 0x0f, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x46, + 0x46, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x45, 0x5f, 0x4f, 0x4e, 0x10, 0x02, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, + 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2d, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2d, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x61, 0x72, 0x65, 0x2d, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -455,28 +506,30 @@ func file_specs_specs_proto_rawDescGZIP() []byte { } var file_specs_specs_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_specs_specs_proto_goTypes = []any{ (BootMode)(0), // 0: baremetalproviderspecs.BootMode (PowerState)(0), // 1: baremetalproviderspecs.PowerState (*PowerManagement)(nil), // 2: baremetalproviderspecs.PowerManagement (*MachineStatusSpec)(nil), // 3: baremetalproviderspecs.MachineStatusSpec - (*PowerManagement_IPMI)(nil), // 4: baremetalproviderspecs.PowerManagement.IPMI - (*PowerManagement_API)(nil), // 5: baremetalproviderspecs.PowerManagement.API - (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp + (*PowerStatusSpec)(nil), // 4: baremetalproviderspecs.PowerStatusSpec + (*PowerManagement_IPMI)(nil), // 5: baremetalproviderspecs.PowerManagement.IPMI + (*PowerManagement_API)(nil), // 6: baremetalproviderspecs.PowerManagement.API + (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp } var file_specs_specs_proto_depIdxs = []int32{ - 4, // 0: baremetalproviderspecs.PowerManagement.ipmi:type_name -> baremetalproviderspecs.PowerManagement.IPMI - 5, // 1: baremetalproviderspecs.PowerManagement.api:type_name -> baremetalproviderspecs.PowerManagement.API + 5, // 0: baremetalproviderspecs.PowerManagement.ipmi:type_name -> baremetalproviderspecs.PowerManagement.IPMI + 6, // 1: baremetalproviderspecs.PowerManagement.api:type_name -> baremetalproviderspecs.PowerManagement.API 2, // 2: baremetalproviderspecs.MachineStatusSpec.power_management:type_name -> baremetalproviderspecs.PowerManagement 1, // 3: baremetalproviderspecs.MachineStatusSpec.power_state:type_name -> baremetalproviderspecs.PowerState 0, // 4: baremetalproviderspecs.MachineStatusSpec.boot_mode:type_name -> baremetalproviderspecs.BootMode - 6, // 5: baremetalproviderspecs.MachineStatusSpec.last_reboot_timestamp:type_name -> google.protobuf.Timestamp - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 7, // 5: baremetalproviderspecs.MachineStatusSpec.last_reboot_timestamp:type_name -> google.protobuf.Timestamp + 1, // 6: baremetalproviderspecs.PowerStatusSpec.power_state:type_name -> baremetalproviderspecs.PowerState + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_specs_specs_proto_init() } @@ -490,7 +543,7 @@ func file_specs_specs_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_specs_specs_proto_rawDesc, NumEnums: 2, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/api/specs/specs.proto b/api/specs/specs.proto index 85ecaff..6915ea1 100644 --- a/api/specs/specs.proto +++ b/api/specs/specs.proto @@ -54,3 +54,7 @@ message MachineStatusSpec { // It is used to track the last reboot time of the machine, and to enforce the MinRebootInterval. google.protobuf.Timestamp last_reboot_timestamp = 5; } + +message PowerStatusSpec { + PowerState power_state = 1; +} diff --git a/api/specs/specs_vtproto.pb.go b/api/specs/specs_vtproto.pb.go index df292c1..e56d43c 100644 --- a/api/specs/specs_vtproto.pb.go +++ b/api/specs/specs_vtproto.pb.go @@ -98,6 +98,23 @@ func (m *MachineStatusSpec) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *PowerStatusSpec) CloneVT() *PowerStatusSpec { + if m == nil { + return (*PowerStatusSpec)(nil) + } + r := new(PowerStatusSpec) + r.PowerState = m.PowerState + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *PowerStatusSpec) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (this *PowerManagement_IPMI) EqualVT(that *PowerManagement_IPMI) bool { if this == that { return true @@ -198,6 +215,25 @@ func (this *MachineStatusSpec) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *PowerStatusSpec) EqualVT(that *PowerStatusSpec) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.PowerState != that.PowerState { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *PowerStatusSpec) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*PowerStatusSpec) + if !ok { + return false + } + return this.EqualVT(that) +} func (m *PowerManagement_IPMI) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -420,6 +456,44 @@ func (m *MachineStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PowerStatusSpec) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PowerStatusSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *PowerStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.PowerState != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.PowerState)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *PowerManagement_IPMI) SizeVT() (n int) { if m == nil { return 0 @@ -505,6 +579,19 @@ func (m *MachineStatusSpec) SizeVT() (n int) { return n } +func (m *PowerStatusSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PowerState != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.PowerState)) + } + n += len(m.unknownFields) + return n +} + func (m *PowerManagement_IPMI) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1070,3 +1157,73 @@ func (m *MachineStatusSpec) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *PowerStatusSpec) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PowerStatusSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PowerStatusSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PowerState", wireType) + } + m.PowerState = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PowerState |= PowerState(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/internal/provider/baremetal/machine_status.go b/internal/provider/baremetal/machine_status.go index 065a45d..3aeee17 100644 --- a/internal/provider/baremetal/machine_status.go +++ b/internal/provider/baremetal/machine_status.go @@ -26,7 +26,7 @@ func NewMachineStatus(id string) *MachineStatus { // MachineStatusType is the type of MachineStatus resource. var MachineStatusType = infra.ResourceType("BareMetalMachineStatus", providermeta.ProviderID) -// MachineStatus describes machineStatus configuration. +// MachineStatus describes machine status configuration. type MachineStatus = typed.Resource[MachineStatusSpec, MachineStatusExtension] // MachineStatusSpec wraps specs.MachineStatusSpec. diff --git a/internal/provider/baremetal/power_status.go b/internal/provider/baremetal/power_status.go new file mode 100644 index 0000000..cea571f --- /dev/null +++ b/internal/provider/baremetal/power_status.go @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package baremetal + +import ( + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "github.com/cosi-project/runtime/pkg/resource/typed" + "github.com/siderolabs/omni/client/pkg/infra" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + providermeta "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/meta" +) + +// NewPowerStatus creates a new PowerStatus. +func NewPowerStatus(id string) *PowerStatus { + return typed.NewResource[PowerStatusSpec, PowerStatusExtension]( + resource.NewMetadata(Namespace, PowerStatusType, id, resource.VersionUndefined), + protobuf.NewResourceSpec(&specs.PowerStatusSpec{}), + ) +} + +// PowerStatusType is the type of PowerStatus resource. +var PowerStatusType = infra.ResourceType("BareMetalPowerStatus", providermeta.ProviderID) + +// PowerStatus describes power status configuration. +type PowerStatus = typed.Resource[PowerStatusSpec, PowerStatusExtension] + +// PowerStatusSpec wraps specs.PowerStatusSpec. +type PowerStatusSpec = protobuf.ResourceSpec[specs.PowerStatusSpec, *specs.PowerStatusSpec] + +// PowerStatusExtension providers auxiliary methods for PowerStatus resource. +type PowerStatusExtension struct{} + +// ResourceDefinition implements [typed.Extension] interface. +func (PowerStatusExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: PowerStatusType, + Aliases: []resource.Type{}, + DefaultNamespace: Namespace, + PrintColumns: []meta.PrintColumn{}, + } +} diff --git a/internal/provider/boot/boot.go b/internal/provider/boot/boot.go index 145d946..422f5b2 100644 --- a/internal/provider/boot/boot.go +++ b/internal/provider/boot/boot.go @@ -21,6 +21,7 @@ type Mode struct { BootMode specs.BootMode Installed bool RequiresPowerMgmtConfig bool + NeedsToBePoweredOn bool } // DetermineRequiredMode determines the required boot mode. @@ -63,6 +64,7 @@ func DetermineRequiredMode(infraMachine *infra.Machine, status *baremetal.Machin requiresWipe := pendingWipeID != "" bootIntoAgentMode := infraMachineTearingDown || acceptancePending || !allocated || requiresPowerMgmtConfig || requiresWipe + needsToBePoweredOn := allocated || requiresPowerMgmtConfig || installed || acceptancePending || infraMachineTearingDown || requiresWipe var requiredBootMode specs.BootMode @@ -85,6 +87,7 @@ func DetermineRequiredMode(infraMachine *infra.Machine, status *baremetal.Machin zap.String("last_wipe_id", lastWipeID), zap.Stringer("acceptance_status", acceptanceStatus), zap.Stringer("required_boot_mode", requiredBootMode), + zap.Bool("needs_to_be_powered_on", needsToBePoweredOn), ).Debug("determined boot mode") return Mode{ @@ -92,5 +95,6 @@ func DetermineRequiredMode(infraMachine *infra.Machine, status *baremetal.Machin BootMode: requiredBootMode, Installed: installed, RequiresPowerMgmtConfig: requiresPowerMgmtConfig, + NeedsToBePoweredOn: needsToBePoweredOn, }, nil } diff --git a/internal/provider/controllers/infra_machine_status.go b/internal/provider/controllers/infra_machine_status.go index 8b753c9..56fad7d 100644 --- a/internal/provider/controllers/infra_machine_status.go +++ b/internal/provider/controllers/infra_machine_status.go @@ -50,14 +50,14 @@ type APIPowerManager interface { ReadManagementAddress(id resource.ID, logger *zap.Logger) (string, error) } -// InfraMachineController manages InfraMachine resource lifecycle. -type InfraMachineController = qtransform.QController[*infra.Machine, *infra.MachineStatus] - -// NewInfraMachineController initializes InfraMachineController. -func NewInfraMachineController(agentService AgentService, apiPowerManager APIPowerManager, state state.State, pxeBootMode pxe.BootMode, - requeueInterval, minRebootInterval time.Duration, -) *InfraMachineController { - helper := &infraMachineControllerHelper{ +// InfraMachineStatusController manages InfraMachine resource lifecycle. +type InfraMachineStatusController = qtransform.QController[*infra.Machine, *infra.MachineStatus] + +// NewInfraMachineStatusController initializes InfraMachineStatusController. +func NewInfraMachineStatusController(agentService AgentService, apiPowerManager APIPowerManager, state state.State, pxeBootMode pxe.BootMode, + requeueInterval time.Duration, minRebootInterval time.Duration, +) *InfraMachineStatusController { + helper := &infraMachineStatusControllerHelper{ agentService: agentService, apiPowerManager: apiPowerManager, state: state, @@ -68,7 +68,7 @@ func NewInfraMachineController(agentService AgentService, apiPowerManager APIPow return qtransform.NewQController( qtransform.Settings[*infra.Machine, *infra.MachineStatus]{ - Name: meta.ProviderID + ".InfraMachineController", + Name: meta.ProviderID + ".InfraMachineStatusController", MapMetadataFunc: func(infraMachine *infra.Machine) *infra.MachineStatus { return infra.NewMachineStatus(infraMachine.Metadata().ID()) }, @@ -92,7 +92,7 @@ func NewInfraMachineController(agentService AgentService, apiPowerManager APIPow ) } -type infraMachineControllerHelper struct { +type infraMachineStatusControllerHelper struct { agentService AgentService apiPowerManager APIPowerManager state state.State @@ -101,7 +101,7 @@ type infraMachineControllerHelper struct { minRebootInterval time.Duration } -func (h *infraMachineControllerHelper) transform(ctx context.Context, reader controller.Reader, logger *zap.Logger, +func (h *infraMachineStatusControllerHelper) transform(ctx context.Context, reader controller.Reader, logger *zap.Logger, infraMachine *infra.Machine, infraMachineStatus *infra.MachineStatus, ) error { preferredPowerState := infraMachine.TypedSpec().Value.PreferredPowerState @@ -201,7 +201,7 @@ func (h *infraMachineControllerHelper) transform(ctx context.Context, reader con // finalizerRemoval is called when the infra.Machine is being deleted. // // We do not need to wipe the disks here, as if/when the machine reconnects to Omni, a new infra.Machine will be created, and it will be marked for the initial wipe. -func (h *infraMachineControllerHelper) finalizerRemoval(ctx context.Context, reader controller.Reader, logger *zap.Logger, infraMachine *infra.Machine) error { +func (h *infraMachineStatusControllerHelper) finalizerRemoval(ctx context.Context, reader controller.Reader, logger *zap.Logger, infraMachine *infra.Machine) error { // attempt to boot into agent mode if it is not already in agent mode status, err := safe.ReaderGetByID[*baremetal.MachineStatus](ctx, reader, infraMachine.Metadata().ID()) if err != nil { @@ -226,7 +226,7 @@ func (h *infraMachineControllerHelper) finalizerRemoval(ctx context.Context, rea } // removeInternalStatus removes the provider-internal baremetal.MachineStatus resource. -func (h *infraMachineControllerHelper) removeInternalStatus(ctx context.Context, id resource.ID) error { +func (h *infraMachineStatusControllerHelper) removeInternalStatus(ctx context.Context, id resource.ID) error { statusMD := baremetal.NewMachineStatus(id).Metadata() destroyReady, err := h.state.Teardown(ctx, statusMD) @@ -253,7 +253,7 @@ func (h *infraMachineControllerHelper) removeInternalStatus(ctx context.Context, return nil } -func (h *infraMachineControllerHelper) populateInfraMachineStatus(status *baremetal.MachineStatus, infraMachineStatus *infra.MachineStatus) error { +func (h *infraMachineStatusControllerHelper) populateInfraMachineStatus(status *baremetal.MachineStatus, infraMachineStatus *infra.MachineStatus) error { infraMachineStatus.TypedSpec().Value.ReadyToUse = false // update power state @@ -271,7 +271,7 @@ func (h *infraMachineControllerHelper) populateInfraMachineStatus(status *bareme return nil } -func (h *infraMachineControllerHelper) wipe(ctx context.Context, id resource.ID, pendingWipeID string, logger *zap.Logger) error { +func (h *infraMachineStatusControllerHelper) wipe(ctx context.Context, id resource.ID, pendingWipeID string, logger *zap.Logger) error { if err := h.agentService.WipeDisks(ctx, id); err != nil { statusCode := grpcstatus.Code(err) if statusCode == codes.Unavailable { @@ -303,7 +303,7 @@ func (h *infraMachineControllerHelper) wipe(ctx context.Context, id resource.ID, } // ensureReboot makes sure that the machine is rebooted if it can be rebooted. -func (h *infraMachineControllerHelper) ensureReboot(ctx context.Context, status *baremetal.MachineStatus, pxeBoot bool, logger *zap.Logger) error { +func (h *infraMachineStatusControllerHelper) ensureReboot(ctx context.Context, status *baremetal.MachineStatus, pxeBoot bool, logger *zap.Logger) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) defer cancel() @@ -349,7 +349,7 @@ func (h *infraMachineControllerHelper) ensureReboot(ctx context.Context, status } // ensurePowerManagement makes sure that the power management for the machine is initialized if it hasn't been done yet. -func (h *infraMachineControllerHelper) ensurePowerManagement(ctx context.Context, status *baremetal.MachineStatus, logger *zap.Logger) error { +func (h *infraMachineStatusControllerHelper) ensurePowerManagement(ctx context.Context, status *baremetal.MachineStatus, logger *zap.Logger) error { logger.Info("initializing power management") id := status.Metadata().ID() @@ -407,7 +407,7 @@ func (h *infraMachineControllerHelper) ensurePowerManagement(ctx context.Context } // ensurePowerManagementOnAgent ensures that the power management (e.g., IPMI) is configured and credentials are set on the Talos machine running agent. -func (h *infraMachineControllerHelper) ensurePowerManagementOnAgent(ctx context.Context, id resource.ID, powerManagement *agentpb.GetPowerManagementResponse) (ipmiPassword string, err error) { +func (h *infraMachineStatusControllerHelper) ensurePowerManagementOnAgent(ctx context.Context, id resource.ID, powerManagement *agentpb.GetPowerManagementResponse) (ipmiPassword string, err error) { var ( api *agentpb.SetPowerManagementRequest_API ipmi *agentpb.SetPowerManagementRequest_IPMI diff --git a/internal/provider/controllers/power_status.go b/internal/provider/controllers/power_status.go new file mode 100644 index 0000000..4d093d4 --- /dev/null +++ b/internal/provider/controllers/power_status.go @@ -0,0 +1,145 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package controllers + +import ( + "context" + + "github.com/cosi-project/runtime/pkg/controller" + "github.com/cosi-project/runtime/pkg/controller/generic/qtransform" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + omnispecs "github.com/siderolabs/omni/client/api/omni/specs" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/boot" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/machinestatus" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/meta" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power" +) + +// PowerStatusController manages InfraMachine resource lifecycle. +type PowerStatusController = qtransform.QController[*infra.Machine, *baremetal.PowerStatus] + +// NewPowerStatusController creates a new PowerStatusController. +func NewPowerStatusController(state state.State) *PowerStatusController { + helper := &powerStatusControllerHelper{ + state: state, + } + + return qtransform.NewQController( + qtransform.Settings[*infra.Machine, *baremetal.PowerStatus]{ + Name: meta.ProviderID + ".PowerStatusController", + MapMetadataFunc: func(infraMachine *infra.Machine) *baremetal.PowerStatus { + return baremetal.NewPowerStatus(infraMachine.Metadata().ID()) + }, + UnmapMetadataFunc: func(powerStatus *baremetal.PowerStatus) *infra.Machine { + return infra.NewMachine(powerStatus.Metadata().ID()) + }, + TransformFunc: helper.transform, + }, + qtransform.WithExtraMappedInput( + func(_ context.Context, _ *zap.Logger, _ controller.QRuntime, status *baremetal.MachineStatus) ([]resource.Pointer, error) { + ptr := infra.NewMachine(status.Metadata().ID()).Metadata() + + return []resource.Pointer{ptr}, nil + }, + ), + qtransform.WithConcurrency(4), + ) +} + +type powerStatusControllerHelper struct { + state state.State +} + +func (helper *powerStatusControllerHelper) transform(ctx context.Context, _ controller.Reader, logger *zap.Logger, machine *infra.Machine, powerStatus *baremetal.PowerStatus) error { + machineStatus, err := machinestatus.Modify(ctx, helper.state, machine.Metadata().ID(), nil) + if err != nil { + return err + } + + machineState, err := safe.StateGetByID[*infra.MachineState](ctx, helper.state, machine.Metadata().ID()) + if err != nil && !state.IsNotFoundError(err) { + return err + } + + mode, err := boot.DetermineRequiredMode(machine, machineStatus, machineState, logger) + if err != nil { + return err + } + + if mode.RequiresPowerMgmtConfig { + logger.Debug("power management is not yet configured") + + powerStatus.TypedSpec().Value.PowerState = specs.PowerState_POWER_STATE_UNKNOWN + + return nil + } + + powerManagement := machineStatus.TypedSpec().Value.PowerManagement + preferredPowerState := machine.TypedSpec().Value.PreferredPowerState + + powerClient, err := power.GetClient(powerManagement) + if err != nil { + return err + } + + isPoweredOn, err := powerClient.IsPoweredOn(ctx) + if err != nil { + return err + } + + switch { + case mode.NeedsToBePoweredOn && preferredPowerState == omnispecs.InfraMachineSpec_POWER_STATE_ON && !isPoweredOn: + logger.Debug("power on machine") + + if err = powerClient.PowerOn(ctx); err != nil { + return err + } + + return helper.updatePowerState(ctx, powerStatus, true, true) + case !mode.NeedsToBePoweredOn && preferredPowerState == omnispecs.InfraMachineSpec_POWER_STATE_OFF && isPoweredOn: + logger.Debug("power off machine") + + if err = powerClient.PowerOff(ctx); err != nil { + return err + } + + return helper.updatePowerState(ctx, powerStatus, true, false) + default: + logger.Debug("machine power state is already as desired", zap.Stringer("power_state", preferredPowerState)) + + return helper.updatePowerState(ctx, powerStatus, isPoweredOn, false) + } +} + +func (helper *powerStatusControllerHelper) updatePowerState(ctx context.Context, status *baremetal.PowerStatus, isPoweredOn, updateLastRebootTimestamp bool) error { + powerState := specs.PowerState_POWER_STATE_OFF + if isPoweredOn { + powerState = specs.PowerState_POWER_STATE_ON + } + + if _, err := machinestatus.Modify(ctx, helper.state, status.Metadata().ID(), func(machineStatus *baremetal.MachineStatus) error { + machineStatus.TypedSpec().Value.PowerState = powerState + + if updateLastRebootTimestamp { + machineStatus.TypedSpec().Value.LastRebootTimestamp = timestamppb.Now() + } + + return nil + }); err != nil { + return err + } + + status.TypedSpec().Value.PowerState = powerState + + return nil +} diff --git a/internal/provider/power/api/api.go b/internal/provider/power/api/api.go index c0f6ae5..5c805fa 100644 --- a/internal/provider/power/api/api.go +++ b/internal/provider/power/api/api.go @@ -32,6 +32,11 @@ func (c *Client) Reboot(ctx context.Context) error { return c.doPost(ctx, "/reboot") } +// PowerOn implements the power.Client interface. +func (c *Client) PowerOn(ctx context.Context) error { + return c.doPost(ctx, "/poweron") +} + // PowerOff implements the power.Client interface. func (c *Client) PowerOff(ctx context.Context) error { return c.doPost(ctx, "/poweroff") diff --git a/internal/provider/power/ipmi/ipmi.go b/internal/provider/power/ipmi/ipmi.go index 918735d..b0ef49a 100644 --- a/internal/provider/power/ipmi/ipmi.go +++ b/internal/provider/power/ipmi/ipmi.go @@ -32,6 +32,11 @@ func (c *Client) Reboot(context.Context) error { return c.ipmiClient.Control(goipmi.ControlPowerCycle) } +// PowerOn implements the power.Client interface. +func (c *Client) PowerOn(context.Context) error { + return c.ipmiClient.Control(goipmi.ControlPowerUp) +} + // PowerOff implements the power.Client interface. func (c *Client) PowerOff(context.Context) error { return c.ipmiClient.Control(goipmi.ControlPowerDown) diff --git a/internal/provider/power/power.go b/internal/provider/power/power.go index ebb9c75..bd1ab05 100644 --- a/internal/provider/power/power.go +++ b/internal/provider/power/power.go @@ -24,6 +24,7 @@ type Client interface { io.Closer Reboot(ctx context.Context) error IsPoweredOn(ctx context.Context) (bool, error) + PowerOn(ctx context.Context) error PowerOff(ctx context.Context) error SetPXEBootOnce(ctx context.Context, mode pxe.BootMode) error } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 29e452b..192871d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,6 +12,7 @@ import ( "os" "time" + "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/controller/runtime" runtimeoptions "github.com/cosi-project/runtime/pkg/controller/runtime/options" "github.com/cosi-project/runtime/pkg/resource/protobuf" @@ -141,10 +142,13 @@ func (p *Provider) Run(ctx context.Context) error { // todo: enable if we re-enable reverse tunnel on Omni: https://github.com/siderolabs/omni/pull/746 // reverseTunnel := tunnel.New(omniState, omniAPIClient, p.logger.With(zap.String("component", "reverse_tunnel"))) - infraMachineController := controllers.NewInfraMachineController(agentService, apiPowerManager, omniState, pxeBootMode, 1*time.Minute, p.options.MinRebootInterval) - - if err = cosiRuntime.RegisterQController(infraMachineController); err != nil { - return fmt.Errorf("failed to register controller: %w", err) + for _, qController := range []controller.QController{ + controllers.NewInfraMachineStatusController(agentService, apiPowerManager, omniState, pxeBootMode, 1*time.Minute, p.options.MinRebootInterval), + controllers.NewPowerStatusController(omniState), + } { + if err = cosiRuntime.RegisterQController(qController); err != nil { + return fmt.Errorf("failed to register QController: %w", err) + } } return p.runComponents(ctx, []component{ @@ -160,14 +164,21 @@ func (p *Provider) Run(ctx context.Context) error { func (p *Provider) buildCOSIRuntime(omniAPIClient *client.Client) (*runtime.Runtime, error) { omniState := omniAPIClient.Omni().State() + var options []runtimeoptions.Option + if err := protobuf.RegisterResource(baremetal.MachineStatusType, &baremetal.MachineStatus{}); err != nil { return nil, fmt.Errorf("failed to register protobuf resource: %w", err) } - var options []runtimeoptions.Option + if err := protobuf.RegisterResource(baremetal.PowerStatusType, &baremetal.PowerStatus{}); err != nil { + return nil, fmt.Errorf("failed to register protobuf resource: %w", err) + } if p.options.EnableResourceCache { - options = append(options, safe.WithResourceCache[*baremetal.MachineStatus]()) + options = append(options, + safe.WithResourceCache[*baremetal.MachineStatus](), + safe.WithResourceCache[*baremetal.PowerStatus](), + ) } cosiRuntime, err := runtime.NewRuntime(omniState, p.logger.With(zap.String("component", "cosi_runtime")), options...) diff --git a/internal/qemu/machines.go b/internal/qemu/machines.go index f059115..bbd554a 100644 --- a/internal/qemu/machines.go +++ b/internal/qemu/machines.go @@ -137,8 +137,6 @@ func (machines *Machines) Run(ctx context.Context) error { } } - <-ctx.Done() - return nil }