diff --git a/client/api/omni/specs/infra.pb.go b/client/api/omni/specs/infra.pb.go index fa539cf6a..027d8336b 100644 --- a/client/api/omni/specs/infra.pb.go +++ b/client/api/omni/specs/infra.pb.go @@ -342,8 +342,16 @@ type InfraMachineSpec struct { ExtraKernelArgs string `protobuf:"bytes,6,opt,name=extra_kernel_args,json=extraKernelArgs,proto3" json:"extra_kernel_args,omitempty"` RequestedRebootId string `protobuf:"bytes,7,opt,name=requested_reboot_id,json=requestedRebootId,proto3" json:"requested_reboot_id,omitempty"` Cordoned bool `protobuf:"varint,8,opt,name=cordoned,proto3" json:"cordoned,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // InstallEventId is a counter, incremented each time Omni receives an event over SideroLink that indicates there is Talos installation on the disk. + // + // This value is then used by the infra provider to make the decision whether Talos is installed or not. + // It is able to track the installation state by: + // - Storing a copy of the value of this counter internally after wiping a machine. + // - Comparing the value of this counter with the stored value to determine if Talos is installed: + // It is installed if the value of the counter is greater than the stored value. + InstallEventId uint64 `protobuf:"varint,9,opt,name=install_event_id,json=installEventId,proto3" json:"install_event_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *InfraMachineSpec) Reset() { @@ -432,6 +440,13 @@ func (x *InfraMachineSpec) GetCordoned() bool { return false } +func (x *InfraMachineSpec) GetInstallEventId() uint64 { + if x != nil { + return x.InstallEventId + } + return 0 +} + type InfraMachineStateSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Installed bool `protobuf:"varint,1,opt,name=installed,proto3" json:"installed,omitempty"` @@ -483,6 +498,7 @@ type InfraMachineStatusSpec struct { ReadyToUse bool `protobuf:"varint,2,opt,name=ready_to_use,json=readyToUse,proto3" json:"ready_to_use,omitempty"` LastRebootId string `protobuf:"bytes,3,opt,name=last_reboot_id,json=lastRebootId,proto3" json:"last_reboot_id,omitempty"` LastRebootTimestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=last_reboot_timestamp,json=lastRebootTimestamp,proto3" json:"last_reboot_timestamp,omitempty"` + Installed bool `protobuf:"varint,5,opt,name=installed,proto3" json:"installed,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -545,6 +561,13 @@ func (x *InfraMachineStatusSpec) GetLastRebootTimestamp() *timestamppb.Timestamp return nil } +func (x *InfraMachineStatusSpec) GetInstalled() bool { + if x != nil { + return x.Installed + } + return false +} + type InfraProviderStatusSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Schema string `protobuf:"bytes,1,opt,name=schema,proto3" json:"schema,omitempty"` @@ -706,7 +729,7 @@ var file_omni_specs_infra_proto_rawDesc = []byte{ 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x02, 0x12, - 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0xf1, 0x03, 0x0a, 0x10, + 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0x9b, 0x04, 0x0a, 0x10, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x5d, 0x0a, 0x15, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, @@ -734,56 +757,61 @@ var file_omni_specs_infra_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x72, 0x64, 0x6f, 0x6e, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x6f, 0x72, 0x64, 0x6f, 0x6e, 0x65, - 0x64, 0x22, 0x3c, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x50, 0x6f, 0x77, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, - 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x46, 0x46, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x50, - 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x4e, 0x10, 0x01, 0x22, - 0x35, 0x0a, 0x15, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x22, 0xd9, 0x02, 0x0a, 0x16, 0x49, 0x6e, 0x66, 0x72, 0x61, - 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, - 0x63, 0x12, 0x50, 0x0a, 0x0b, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x49, - 0x6e, 0x66, 0x72, 0x61, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x50, 0x6f, 0x77, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x74, 0x6f, 0x5f, - 0x75, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x79, - 0x54, 0x6f, 0x55, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, - 0x61, 0x73, 0x74, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x4e, 0x0a, 0x15, 0x6c, - 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 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, 0x22, 0x55, 0x0a, 0x11, 0x4d, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x3c, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 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, 0x22, 0x7b, 0x0a, 0x17, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, - 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x22, - 0x8b, 0x01, 0x0a, 0x1d, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, - 0x63, 0x12, 0x54, 0x0a, 0x18, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, - 0x65, 0x61, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 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, - 0x16, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x32, 0x5a, - 0x30, 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, 0x2f, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x4f, 0x46, 0x46, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x4e, 0x10, 0x01, 0x22, 0x35, 0x0a, 0x15, 0x49, 0x6e, 0x66, + 0x72, 0x61, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, + 0x65, 0x63, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, + 0x22, 0xf7, 0x02, 0x0a, 0x16, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x50, 0x0a, 0x0b, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2f, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x49, 0x6e, 0x66, 0x72, 0x61, 0x4d, 0x61, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x2e, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, + 0x0c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x79, 0x54, 0x6f, 0x55, 0x73, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x62, + 0x6f, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x4e, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 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, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 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, 0x22, 0x7b, 0x0a, 0x17, 0x49, 0x6e, + 0x66, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x22, 0x8b, 0x01, 0x0a, 0x1d, 0x49, 0x6e, 0x66, 0x72, + 0x61, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x54, 0x0a, 0x18, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 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, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, + 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x32, 0x5a, 0x30, 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, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, + 0x6d, 0x6e, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/client/api/omni/specs/infra.proto b/client/api/omni/specs/infra.proto index bc6112eed..02d8ba5c2 100644 --- a/client/api/omni/specs/infra.proto +++ b/client/api/omni/specs/infra.proto @@ -49,6 +49,15 @@ message InfraMachineSpec { string extra_kernel_args = 6; string requested_reboot_id = 7; bool cordoned = 8; + + // InstallEventId is a counter, incremented each time Omni receives an event over SideroLink that indicates there is Talos installation on the disk. + // + // This value is then used by the infra provider to make the decision whether Talos is installed or not. + // It is able to track the installation state by: + // - Storing a copy of the value of this counter internally after wiping a machine. + // - Comparing the value of this counter with the stored value to determine if Talos is installed: + // It is installed if the value of the counter is greater than the stored value. + uint64 install_event_id = 9; } message InfraMachineStateSpec { @@ -68,6 +77,7 @@ message InfraMachineStatusSpec { bool ready_to_use = 2; string last_reboot_id = 3; google.protobuf.Timestamp last_reboot_timestamp = 4; + bool installed = 5; } message InfraProviderStatusSpec { diff --git a/client/api/omni/specs/infra_vtproto.pb.go b/client/api/omni/specs/infra_vtproto.pb.go index 6e4d6447b..b2845cb07 100644 --- a/client/api/omni/specs/infra_vtproto.pb.go +++ b/client/api/omni/specs/infra_vtproto.pb.go @@ -91,6 +91,7 @@ func (m *InfraMachineSpec) CloneVT() *InfraMachineSpec { r.ExtraKernelArgs = m.ExtraKernelArgs r.RequestedRebootId = m.RequestedRebootId r.Cordoned = m.Cordoned + r.InstallEventId = m.InstallEventId if rhs := m.Extensions; rhs != nil { tmpContainer := make([]string, len(rhs)) copy(tmpContainer, rhs) @@ -133,6 +134,7 @@ func (m *InfraMachineStatusSpec) CloneVT() *InfraMachineStatusSpec { r.ReadyToUse = m.ReadyToUse r.LastRebootId = m.LastRebootId r.LastRebootTimestamp = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.LastRebootTimestamp).CloneVT()) + r.Installed = m.Installed if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -309,6 +311,9 @@ func (this *InfraMachineSpec) EqualVT(that *InfraMachineSpec) bool { if this.Cordoned != that.Cordoned { return false } + if this.InstallEventId != that.InstallEventId { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -356,6 +361,9 @@ func (this *InfraMachineStatusSpec) EqualVT(that *InfraMachineStatusSpec) bool { if !(*timestamppb1.Timestamp)(this.LastRebootTimestamp).EqualVT((*timestamppb1.Timestamp)(that.LastRebootTimestamp)) { return false } + if this.Installed != that.Installed { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -597,6 +605,11 @@ func (m *InfraMachineSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.InstallEventId != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.InstallEventId)) + i-- + dAtA[i] = 0x48 + } if m.Cordoned { i-- if m.Cordoned { @@ -730,6 +743,16 @@ func (m *InfraMachineStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Installed { + i-- + if m.Installed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } if m.LastRebootTimestamp != nil { size, err := (*timestamppb1.Timestamp)(m.LastRebootTimestamp).MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -981,6 +1004,9 @@ func (m *InfraMachineSpec) SizeVT() (n int) { if m.Cordoned { n += 2 } + if m.InstallEventId != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.InstallEventId)) + } n += len(m.unknownFields) return n } @@ -1018,6 +1044,9 @@ func (m *InfraMachineStatusSpec) SizeVT() (n int) { l = (*timestamppb1.Timestamp)(m.LastRebootTimestamp).SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if m.Installed { + n += 2 + } n += len(m.unknownFields) return n } @@ -1747,6 +1776,25 @@ func (m *InfraMachineSpec) UnmarshalVT(dAtA []byte) error { } } m.Cordoned = bool(v != 0) + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InstallEventId", wireType) + } + m.InstallEventId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.InstallEventId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -1976,6 +2024,26 @@ func (m *InfraMachineStatusSpec) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Installed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Installed = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/client/pkg/omni/resources/infra/infra.go b/client/pkg/omni/resources/infra/infra.go index cde158876..6d57c715d 100644 --- a/client/pkg/omni/resources/infra/infra.go +++ b/client/pkg/omni/resources/infra/infra.go @@ -10,7 +10,6 @@ import "github.com/siderolabs/omni/client/pkg/omni/resources/registry" func init() { registry.MustRegisterResource(MachineRequestType, &MachineRequest{}) registry.MustRegisterResource(MachineRequestStatusType, &MachineRequestStatus{}) - registry.MustRegisterResource(InfraMachineStateType, &MachineState{}) registry.MustRegisterResource(InfraMachineType, &Machine{}) registry.MustRegisterResource(InfraMachineStatusType, &MachineStatus{}) registry.MustRegisterResource(InfraProviderStatusType, &ProviderStatus{}) diff --git a/client/pkg/omni/resources/infra/machine.go b/client/pkg/omni/resources/infra/machine.go index 3d60db720..89967052b 100644 --- a/client/pkg/omni/resources/infra/machine.go +++ b/client/pkg/omni/resources/infra/machine.go @@ -77,6 +77,10 @@ func (MachineExtension) ResourceDefinition() meta.ResourceDefinitionSpec { Name: "Cordoned", JSONPath: "{.cordoned}", }, + { + Name: "Install Event ID", + JSONPath: "{.installeventid}", + }, }, } } diff --git a/client/pkg/omni/resources/infra/machine_state.go b/client/pkg/omni/resources/infra/machine_state.go deleted file mode 100644 index 87bd2ec15..000000000 --- a/client/pkg/omni/resources/infra/machine_state.go +++ /dev/null @@ -1,56 +0,0 @@ -// 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 infra - -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/api/omni/specs" - "github.com/siderolabs/omni/client/pkg/omni/resources" -) - -// NewMachineState creates a new MachineState resource. -func NewMachineState(id string) *MachineState { - return typed.NewResource[MachineStateSpec, MachineStateExtension]( - resource.NewMetadata(resources.InfraProviderNamespace, InfraMachineStateType, id, resource.VersionUndefined), - protobuf.NewResourceSpec(&specs.InfraMachineStateSpec{}), - ) -} - -const ( - // InfraMachineStateType is the type of MachineState resource. - // - // tsgen:InfraMachineStateType - InfraMachineStateType = resource.Type("InfraMachineStates.omni.sidero.dev") -) - -// MachineState resource describes an infra machine state. -// -// It is a shared resource between the respective infra provider and Omni - both can read and write it. -type MachineState = typed.Resource[MachineStateSpec, MachineStateExtension] - -// MachineStateSpec wraps specs.MachineStateSpec. -type MachineStateSpec = protobuf.ResourceSpec[specs.InfraMachineStateSpec, *specs.InfraMachineStateSpec] - -// MachineStateExtension providers auxiliary methods for MachineState resource. -type MachineStateExtension struct{} - -// ResourceDefinition implements [typed.Extension] interface. -func (MachineStateExtension) ResourceDefinition() meta.ResourceDefinitionSpec { - return meta.ResourceDefinitionSpec{ - Type: InfraMachineStateType, - Aliases: []resource.Type{}, - DefaultNamespace: resources.InfraProviderNamespace, - PrintColumns: []meta.PrintColumn{ - { - Name: "Installed", - JSONPath: "{.installed}", - }, - }, - } -} diff --git a/client/pkg/omni/resources/infra/machine_status.go b/client/pkg/omni/resources/infra/machine_status.go index e66320c5c..5b0bf791a 100644 --- a/client/pkg/omni/resources/infra/machine_status.go +++ b/client/pkg/omni/resources/infra/machine_status.go @@ -58,9 +58,13 @@ func (MachineStatusExtension) ResourceDefinition() meta.ResourceDefinitionSpec { JSONPath: "{.lastrebootid}", }, { - Name: "Last Reboot Timestamp", + Name: "Last Reboot At", JSONPath: "{.lastreboottimestamp}", }, + { + Name: "Installed", + JSONPath: "{.installed}", + }, }, } } diff --git a/cmd/integration-test/pkg/tests/blocks.go b/cmd/integration-test/pkg/tests/blocks.go index 043063c3c..ab35d1574 100644 --- a/cmd/integration-test/pkg/tests/blocks.go +++ b/cmd/integration-test/pkg/tests/blocks.go @@ -259,7 +259,7 @@ func TestGroupClusterCreateAndReady( ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), clusterName, options.InfraProvider != ""), + AssertDestroyCluster(ctx, rootClient.Omni().State(), clusterName, options.InfraProvider != "", false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), clusterName), diff --git a/cmd/integration-test/pkg/tests/cluster.go b/cmd/integration-test/pkg/tests/cluster.go index c2f66b2bb..e1c245a6f 100644 --- a/cmd/integration-test/pkg/tests/cluster.go +++ b/cmd/integration-test/pkg/tests/cluster.go @@ -27,6 +27,7 @@ import ( "github.com/siderolabs/omni/client/api/omni/specs" "github.com/siderolabs/omni/client/pkg/client" "github.com/siderolabs/omni/client/pkg/omni/resources" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" "github.com/siderolabs/omni/client/pkg/omni/resources/siderolink" "github.com/siderolabs/omni/client/pkg/omni/resources/virtual" @@ -555,7 +556,7 @@ func DestroyCluster(testCtx context.Context, st state.State, clusterName string) } // AssertDestroyCluster destroys a cluster and verifies that all dependent resources are gone. -func AssertDestroyCluster(testCtx context.Context, st state.State, clusterName string, expectMachinesRemoved bool) TestFunc { +func AssertDestroyCluster(testCtx context.Context, st state.State, clusterName string, expectMachinesRemoved, assertInfraMachinesState bool) TestFunc { return func(t *testing.T) { ctx, cancel := context.WithTimeout(testCtx, 300*time.Second) defer cancel() @@ -604,6 +605,24 @@ func AssertDestroyCluster(testCtx context.Context, st state.State, clusterName s "machine %q: available %v, bound %v, cluster %q", machine.Metadata().ID(), isAvailable, machineBound, machineCluster, ) }) + + if assertInfraMachinesState { + rtestutils.AssertResources(ctx, t, st, clusterMachineIDs, func(res *infra.Machine, assertion *assert.Assertions) { + assertion.Empty(res.TypedSpec().Value.ClusterTalosVersion) // unallocated + assertion.Empty(res.TypedSpec().Value.Extensions) + + if assertion.NotEmpty(res.TypedSpec().Value.WipeId) { // the machine should be marked for wipe + t.Logf("machine %q is marked for wipe: %s", res.Metadata().ID(), res.TypedSpec().Value.WipeId) + } + }) + + // the provider will wipe the machine and sets the Installed field to false + // after the machine is wiped, ReadyToUse field will be set to true + rtestutils.AssertResources(ctx, t, st, clusterMachineIDs, func(res *infra.MachineStatus, assertion *assert.Assertions) { + assertion.False(res.TypedSpec().Value.Installed) + assertion.True(res.TypedSpec().Value.ReadyToUse) + }) + } } } diff --git a/cmd/integration-test/pkg/tests/infra.go b/cmd/integration-test/pkg/tests/infra.go index 027dd06f0..01c6c23a0 100644 --- a/cmd/integration-test/pkg/tests/infra.go +++ b/cmd/integration-test/pkg/tests/infra.go @@ -322,54 +322,12 @@ func AssertInfraMachinesAreAllocated(testCtx context.Context, omniState state.St rtestutils.AssertResource[*infra.MachineStatus](ctx, t, omniState, id, func(res *infra.MachineStatus, assertion *assert.Assertions) { assertion.Equal(specs.InfraMachineStatusSpec_POWER_STATE_ON, res.TypedSpec().Value.PowerState) assertion.True(res.TypedSpec().Value.ReadyToUse) - }) - - // Omni receives a SequenceEvent from the SideroLink event sink and sets the Installed field to true - rtestutils.AssertResource[*infra.MachineState](ctx, t, omniState, id, func(res *infra.MachineState, assertion *assert.Assertions) { assertion.True(res.TypedSpec().Value.Installed) }) } } } -// AssertAllInfraMachinesAreUnallocated asserts that all infra machines are unallocated. -func AssertAllInfraMachinesAreUnallocated(testCtx context.Context, omniState state.State) TestFunc { - return func(t *testing.T) { - logger := zaptest.NewLogger(t) - - ctx, cancel := context.WithTimeout(testCtx, time.Minute*10) - defer cancel() - - infraMachineList, err := safe.StateListAll[*infra.Machine](ctx, omniState) - require.NoError(t, err) - - require.Greater(t, infraMachineList.Len(), 0) - - for infraMachine := range infraMachineList.All() { - id := infraMachine.Metadata().ID() - - rtestutils.AssertResource[*infra.Machine](ctx, t, omniState, id, func(res *infra.Machine, assertion *assert.Assertions) { - assertion.Empty(res.TypedSpec().Value.ClusterTalosVersion) - assertion.Empty(res.TypedSpec().Value.Extensions) - - if assertion.NotEmpty(res.TypedSpec().Value.WipeId) { // the machine should be marked for wipe - logger.Info("machine is marked for wipe", zap.String("machine_id", id), zap.String("wipe_id", res.TypedSpec().Value.WipeId)) - } - }) - - // provider wipes the machine and sets the Installed field to false - rtestutils.AssertResource[*infra.MachineState](ctx, t, omniState, id, func(res *infra.MachineState, assertion *assert.Assertions) { - assertion.False(res.TypedSpec().Value.Installed) - }) - - // after the machine is wiped, ReadyToUse field will be set to true - rtestutils.AssertResource[*infra.MachineStatus](ctx, t, omniState, id, func(res *infra.MachineStatus, assertion *assert.Assertions) { - assertion.True(res.TypedSpec().Value.ReadyToUse) - }) - } - } -} - // DestroyInfraMachines removes siderolink.Link resources for all machines managed by a static infra provider, // and asserts that the related infra.Machine and infra.MachineStatus resources are deleted. func DestroyInfraMachines(testCtx context.Context, omniState state.State, providerID string, count int) TestFunc { diff --git a/cmd/integration-test/pkg/tests/talos.go b/cmd/integration-test/pkg/tests/talos.go index a430624d0..42ad0a90c 100644 --- a/cmd/integration-test/pkg/tests/talos.go +++ b/cmd/integration-test/pkg/tests/talos.go @@ -743,7 +743,7 @@ func AssertMachineShouldBeUpgradedInMaintenanceMode( AssertTalosVersion(testCtx, rootClient, clusterName, talosVersion2, talosAPIKeyPrepare) // destroy the cluster - AssertDestroyCluster(testCtx, st, clusterName, false)(t) + AssertDestroyCluster(testCtx, st, clusterName, false, false)(t) t.Logf("creating a cluster on version %s using same machines", talosVersion2) @@ -765,7 +765,7 @@ func AssertMachineShouldBeUpgradedInMaintenanceMode( AssertClusterStatusReady(testCtx, st, clusterName)(t) AssertTalosVersion(testCtx, rootClient, clusterName, talosVersion2, talosAPIKeyPrepare) - AssertDestroyCluster(testCtx, st, clusterName, false)(t) + AssertDestroyCluster(testCtx, st, clusterName, false, false)(t) } } diff --git a/cmd/integration-test/pkg/tests/tests.go b/cmd/integration-test/pkg/tests/tests.go index df192cd3b..286be71ba 100644 --- a/cmd/integration-test/pkg/tests/tests.go +++ b/cmd/integration-test/pkg/tests/tests.go @@ -245,7 +245,7 @@ Verify various omnictl commands.`, ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-k8s-node-audit", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-k8s-node-audit", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-k8s-node-audit"), @@ -305,7 +305,7 @@ In the tests, we wipe and reboot the VMs to bring them back as available for the ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-forced-removal", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-forced-removal", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-forced-removal"), @@ -332,7 +332,7 @@ Regression test: create a cluster and destroy it without waiting for the cluster }, subTest{ "ClusterShouldBeDestroyedImmediately", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-immediate", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-immediate", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-immediate"), @@ -500,7 +500,7 @@ In between the scaling operations, assert that the cluster is ready and accessib ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling"), @@ -620,7 +620,7 @@ In between the scaling operations, assert that the cluster is ready and accessib ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling-machine-class-based-machine-sets", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling-machine-class-based-machine-sets", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling-machine-class-based-machine-sets"), @@ -747,7 +747,7 @@ In between the scaling operations, assert that the cluster is ready and accessib ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling-auto-provision", true), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling-auto-provision", true, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-scaling-auto-provision"), @@ -796,7 +796,7 @@ Tests rolling update & scale down strategies for concurrency control for worker ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-rolling-update-parallelism", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-rolling-update-parallelism", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-rolling-update-parallelism"), @@ -854,7 +854,7 @@ In between the scaling operations, assert that the cluster is ready and accessib ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-replace-cp", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-replace-cp", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-replace-cp"), @@ -946,7 +946,7 @@ Tests applying various config patching, including "broken" config patches which ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-config-patching", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-config-patching", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-config-patching"), @@ -1050,7 +1050,7 @@ Tests upgrading Talos version, including reverting a failed upgrade.`, ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-talos-upgrade", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-talos-upgrade", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-talos-upgrade"), @@ -1119,7 +1119,7 @@ Tests upgrading Kubernetes version, including reverting a failed upgrade.`, ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-k8s-upgrade", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-k8s-upgrade", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-k8s-upgrade"), @@ -1193,7 +1193,7 @@ Finally, a completely new cluster is created using the same backup to test the " }, subTest{ "NewClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-etcd-backup-new-cluster", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-etcd-backup-new-cluster", false, false), }, ).Append( TestBlockRestoreEtcdFromLatestBackup(ctx, rootClient, talosAPIKeyPrepare, options, 3, @@ -1204,7 +1204,7 @@ Finally, a completely new cluster is created using the same backup to test the " ).Append( subTest{ "RestoredClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-etcd-backup", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-etcd-backup", false, false), }, ), Finalizer: func(t *testing.T) { @@ -1314,7 +1314,7 @@ Test authorization on accessing Omni API, some tests run without a cluster, some ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-auth", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-auth", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-auth"), @@ -1373,7 +1373,7 @@ Test flow of cluster creation and scaling using cluster templates.`, ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-workload-proxy", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-workload-proxy", false, false), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-workload-proxy"), @@ -1467,12 +1467,7 @@ Note: this test expects all machines to be provisioned by the bare-metal infra p ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-static-infra-provider", false), - }, - ).Append( - subTest{ - "MachinesShouldBeUnallocated", - AssertAllInfraMachinesAreUnallocated(ctx, rootClient.Omni().State()), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-static-infra-provider", false, true), }, ).Append( subTest{ @@ -1499,7 +1494,7 @@ Note: this test expects all machines to be provisioned by the bare-metal infra p ).Append( subTest{ "ClusterShouldBeDestroyed", - AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-static-infra-provider", false), + AssertDestroyCluster(ctx, rootClient.Omni().State(), "integration-static-infra-provider", false, true), }, ), Finalizer: DestroyCluster(ctx, rootClient.Omni().State(), "integration-static-infra-provider"), diff --git a/cmd/omni/cmd/cmd.go b/cmd/omni/cmd/cmd.go index b43271617..c265ad9e5 100644 --- a/cmd/omni/cmd/cmd.go +++ b/cmd/omni/cmd/cmd.go @@ -16,6 +16,7 @@ import ( "sync" "syscall" + "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/state" "github.com/go-logr/zapr" "github.com/prometheus/client_golang/prometheus" @@ -191,6 +192,7 @@ func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtua linkCounterDeltaCh := make(chan siderolink.LinkCounterDeltas) siderolinkEventsCh := make(chan *omnires.MachineStatusSnapshot) + installEventCh := make(chan resource.ID) defaultDiscoveryClient, err := discovery.NewClient(discovery.Options{ UseEmbeddedDiscoveryService: false, @@ -217,7 +219,7 @@ func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtua }() omniRuntime, err := omni.New(talosClientFactory, dnsService, workloadProxyReconciler, resourceLogger, - imageFactoryClient, linkCounterDeltaCh, siderolinkEventsCh, resourceState, virtualState, + imageFactoryClient, linkCounterDeltaCh, siderolinkEventsCh, installEventCh, resourceState, virtualState, prometheus.DefaultRegisterer, defaultDiscoveryClient, embeddedDiscoveryClient, logger.With(logging.Component("omni_runtime"))) if err != nil { return fmt.Errorf("failed to set up the controller runtime: %w", err) @@ -268,6 +270,7 @@ func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtua imageFactoryClient, linkCounterDeltaCh, siderolinkEventsCh, + installEventCh, omniRuntime, talosRuntime, logHandler, diff --git a/frontend/src/api/omni/specs/infra.pb.ts b/frontend/src/api/omni/specs/infra.pb.ts index 300a8cfbb..814b084ef 100644 --- a/frontend/src/api/omni/specs/infra.pb.ts +++ b/frontend/src/api/omni/specs/infra.pb.ts @@ -51,6 +51,7 @@ export type InfraMachineSpec = { extra_kernel_args?: string requested_reboot_id?: string cordoned?: boolean + install_event_id?: string } export type InfraMachineStateSpec = { @@ -62,6 +63,7 @@ export type InfraMachineStatusSpec = { ready_to_use?: boolean last_reboot_id?: string last_reboot_timestamp?: GoogleProtobufTimestamp.Timestamp + installed?: boolean } export type InfraProviderStatusSpec = { diff --git a/frontend/src/api/resources.ts b/frontend/src/api/resources.ts index e13f5eaf8..6e0c828d7 100644 --- a/frontend/src/api/resources.ts +++ b/frontend/src/api/resources.ts @@ -196,7 +196,6 @@ export const ConfigPatchRequestType = "ConfigPatchRequests.omni.sidero.dev"; export const InfraMachineType = "InfraMachines.omni.sidero.dev"; export const MachineRequestType = "MachineRequests.omni.sidero.dev"; export const MachineRequestStatusType = "MachineRequestStatuses.omni.sidero.dev"; -export const InfraMachineStateType = "InfraMachineStates.omni.sidero.dev"; export const InfraMachineStatusType = "InfraMachineStatuses.omni.sidero.dev"; export const InfraProviderHealthStatusType = "InfraProviderHealthStatuses.omni.sidero.dev"; export const InfraProviderStatusType = "InfraProviderStatuses.omni.sidero.dev"; diff --git a/internal/backend/grpc/configs_test.go b/internal/backend/grpc/configs_test.go index d9ab5fa0a..58ab77704 100644 --- a/internal/backend/grpc/configs_test.go +++ b/internal/backend/grpc/configs_test.go @@ -59,7 +59,7 @@ func TestGenerateConfigs(t *testing.T) { logger := zaptest.NewLogger(t) rt, err := omniruntime.New(nil, nil, nil, nil, - nil, nil, nil, st, nil, prometheus.NewRegistry(), nil, nil, logger) + nil, nil, nil, nil, st, nil, prometheus.NewRegistry(), nil, nil, logger) require.NoError(t, err) runtime.Install(omniruntime.Name, rt) diff --git a/internal/backend/grpc/grpc_test.go b/internal/backend/grpc/grpc_test.go index 64bbab329..2e92eb924 100644 --- a/internal/backend/grpc/grpc_test.go +++ b/internal/backend/grpc/grpc_test.go @@ -94,7 +94,7 @@ func (suite *GrpcSuite) SetupTest() { workloadProxyReconciler := workloadproxy.NewReconciler(logger, zap.InfoLevel) suite.runtime, err = omniruntime.New(clientFactory, dnsService, workloadProxyReconciler, nil, - imageFactoryClient, nil, nil, suite.state, nil, prometheus.NewRegistry(), discoveryServiceClientMock, nil, logger) + imageFactoryClient, nil, nil, nil, suite.state, nil, prometheus.NewRegistry(), discoveryServiceClientMock, nil, logger) suite.Require().NoError(err) runtime.Install(omniruntime.Name, suite.runtime) diff --git a/internal/backend/runtime/omni/controllers/omni/infra_machine.go b/internal/backend/runtime/omni/controllers/omni/infra_machine.go index de3615f43..470011d9f 100644 --- a/internal/backend/runtime/omni/controllers/omni/infra_machine.go +++ b/internal/backend/runtime/omni/controllers/omni/infra_machine.go @@ -11,12 +11,11 @@ import ( "slices" "github.com/cosi-project/runtime/pkg/controller" - "github.com/cosi-project/runtime/pkg/controller/generic/qtransform" + "github.com/cosi-project/runtime/pkg/controller/generic" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" "github.com/google/uuid" - "github.com/siderolabs/gen/xerrors" "github.com/siderolabs/gen/xiter" "go.uber.org/zap" @@ -34,77 +33,165 @@ const InfraMachineControllerName = "InfraMachineController" // InfraMachineController manages InfraMachine resource lifecycle. // // InfraMachineController transforms an Omni Machine managed by a static infra provider to an infra.Machine, applying the user overrides in omni.InfraMachineConfig resource if present. -type InfraMachineController = qtransform.QController[*siderolink.Link, *infra.Machine] +type InfraMachineController struct { + installEventCh <-chan resource.ID + generic.NamedController +} -// NewInfraMachineController initializes InfraMachineController. -func NewInfraMachineController() *InfraMachineController { - helper := &infraMachineControllerHelper{} +// NewInfraMachineController creates a new InfraMachineController. +func NewInfraMachineController(installEventCh <-chan resource.ID) *InfraMachineController { + return &InfraMachineController{ + installEventCh: installEventCh, + NamedController: generic.NamedController{ + ControllerName: InfraMachineControllerName, + }, + } +} - return qtransform.NewQController( - qtransform.Settings[*siderolink.Link, *infra.Machine]{ - Name: InfraMachineControllerName, - MapMetadataFunc: func(link *siderolink.Link) *infra.Machine { - return infra.NewMachine(link.Metadata().ID()) +// Settings implements the controller.QController interface. +func (ctrl *InfraMachineController) Settings() controller.QSettings { + return controller.QSettings{ + Inputs: []controller.Input{ + { + Namespace: resources.DefaultNamespace, + Type: siderolink.LinkType, + Kind: controller.InputQPrimary, + }, + { + Namespace: resources.InfraProviderNamespace, + Type: infra.InfraMachineType, + Kind: controller.InputQMappedDestroyReady, + }, + { + Namespace: resources.DefaultNamespace, + Type: omni.InfraMachineConfigType, + Kind: controller.InputQMapped, + }, + { + Namespace: resources.DefaultNamespace, + Type: omni.SchematicConfigurationType, + Kind: controller.InputQMapped, + }, + { + Namespace: resources.DefaultNamespace, + Type: omni.MachineExtensionsType, + Kind: controller.InputQMapped, + }, + { + Namespace: resources.DefaultNamespace, + Type: omni.ClusterMachineType, + Kind: controller.InputQMapped, + }, + { + Namespace: resources.DefaultNamespace, + Type: omni.MachineStatusType, + Kind: controller.InputQMapped, + }, + { + Namespace: resources.InfraProviderNamespace, + Type: infra.InfraProviderStatusType, + Kind: controller.InputQMapped, }, - UnmapMetadataFunc: func(infraMachine *infra.Machine) *siderolink.Link { - return siderolink.NewLink(resources.DefaultNamespace, infraMachine.Metadata().ID(), nil) + }, + Outputs: []controller.Output{ + { + Kind: controller.OutputExclusive, + Type: infra.InfraMachineType, }, - TransformExtraOutputFunc: helper.transformExtraOutput, - FinalizerRemovalExtraOutputFunc: helper.finalizerRemovalExtraOutput, }, - qtransform.WithExtraMappedInput( - qtransform.MapperSameID[*omni.InfraMachineConfig, *siderolink.Link](), - ), - qtransform.WithExtraMappedInput( - qtransform.MapperSameID[*omni.SchematicConfiguration, *siderolink.Link](), - ), - qtransform.WithExtraMappedInput( - qtransform.MapperSameID[*omni.MachineExtensions, *siderolink.Link](), - ), - qtransform.WithExtraMappedInput( - qtransform.MapperSameID[*omni.ClusterMachine, *siderolink.Link](), - ), - qtransform.WithExtraMappedInput( - qtransform.MapperSameID[*omni.MachineStatus, *siderolink.Link](), - ), - qtransform.WithExtraMappedInput( - func(ctx context.Context, _ *zap.Logger, runtime controller.QRuntime, res *infra.ProviderStatus) ([]resource.Pointer, error) { - linkList, err := safe.ReaderListAll[*siderolink.Link](ctx, runtime, state.WithLabelQuery(resource.LabelEqual(omni.LabelInfraProviderID, res.Metadata().ID()))) - if err != nil { - return nil, err + RunHook: func(ctx context.Context, _ *zap.Logger, r controller.QRuntime) error { + for { + select { + case <-ctx.Done(): + return nil + case machineID := <-ctrl.installEventCh: + if err := ctrl.handleInstallEvent(ctx, r, machineID); err != nil { + return err + } } + } + }, + } +} - ptrSeq := xiter.Map(func(in *siderolink.Link) resource.Pointer { - return in.Metadata() - }, linkList.All()) +// Reconcile implements the controller.QController interface. +func (ctrl *InfraMachineController) Reconcile(ctx context.Context, _ *zap.Logger, r controller.QRuntime, ptr resource.Pointer) error { + link, err := safe.ReaderGet[*siderolink.Link](ctx, r, ptr) + if err != nil { + if !state.IsNotFoundError(err) { + return err + } - return slices.Collect(ptrSeq), nil - }, - ), - ) + // link is not found, so we prepare a fake link resource to trigger teardown logic + link = siderolink.NewLink(ptr.Namespace(), ptr.ID(), nil) + link.Metadata().SetPhase(resource.PhaseTearingDown) + } + + if link.Metadata().Phase() == resource.PhaseTearingDown { + return ctrl.reconcileTearingDown(ctx, r, link) + } + + return ctrl.reconcileRunning(ctx, r, link) } -type infraMachineControllerHelper struct{} +func (ctrl *InfraMachineController) reconcileTearingDown(ctx context.Context, r controller.QRuntime, link *siderolink.Link) error { + if _, err := helpers.HandleInput[*omni.InfraMachineConfig](ctx, r, ctrl.Name(), link); err != nil { + return err + } + + if _, err := helpers.HandleInput[*omni.MachineExtensions](ctx, r, ctrl.Name(), link); err != nil { + return err + } + + if _, err := helpers.HandleInput[*omni.ClusterMachine](ctx, r, ctrl.Name(), link); err != nil { + return err + } + + if _, err := helpers.HandleInput[*omni.MachineStatus](ctx, r, ctrl.Name(), link); err != nil { + return err + } + + md := infra.NewMachine(link.Metadata().ID()).Metadata() + + ready, err := r.Teardown(ctx, md) + if err != nil { + if state.IsNotFoundError(err) { + return r.RemoveFinalizer(ctx, link.Metadata(), ctrl.Name()) + } + + return err + } + + if !ready { + return nil + } + + if err = r.Destroy(ctx, md); err != nil { + return err + } + + return r.RemoveFinalizer(ctx, link.Metadata(), ctrl.Name()) +} -func (h *infraMachineControllerHelper) transformExtraOutput(ctx context.Context, r controller.ReaderWriter, _ *zap.Logger, link *siderolink.Link, infraMachine *infra.Machine) error { - config, err := helpers.HandleInput[*omni.InfraMachineConfig](ctx, r, InfraMachineControllerName, link) +func (ctrl *InfraMachineController) reconcileRunning(ctx context.Context, r controller.QRuntime, link *siderolink.Link) error { + config, err := helpers.HandleInput[*omni.InfraMachineConfig](ctx, r, ctrl.Name(), link) if err != nil { return err } - machineExts, err := helpers.HandleInput[*omni.MachineExtensions](ctx, r, InfraMachineControllerName, link) + machineExts, err := helpers.HandleInput[*omni.MachineExtensions](ctx, r, ctrl.Name(), link) if err != nil { return err } - machineStatus, err := helpers.HandleInput[*omni.MachineStatus](ctx, r, InfraMachineControllerName, link) + machineStatus, err := helpers.HandleInput[*omni.MachineStatus](ctx, r, ctrl.Name(), link) if err != nil { return err } providerID, ok := link.Metadata().Annotations().Get(omni.LabelInfraProviderID) if !ok { - return xerrors.NewTaggedf[qtransform.SkipReconcileTag]("the link is not created by an infra provider") + return nil // the link is not created by an infra provider } providerStatus, err := safe.ReaderGetByID[*infra.ProviderStatus](ctx, r, providerID) @@ -113,18 +200,44 @@ func (h *infraMachineControllerHelper) transformExtraOutput(ctx context.Context, } if _, isStaticProvider := providerStatus.Metadata().Labels().Get(omni.LabelIsStaticInfraProvider); !isStaticProvider { - return xerrors.NewTaggedf[qtransform.SkipReconcileTag]("the link is not created by a static infra provider") + return nil // the link is not created by a static infra provider } machineInfoCollected := machineStatus != nil && machineStatus.TypedSpec().Value.SecureBootStatus != nil - if err = h.applyInfraMachineConfig(infraMachine, config, machineInfoCollected); err != nil { + helper := &infraMachineControllerHelper{ + config: config, + machineExts: machineExts, + link: link, + runtime: r, + machineInfoCollected: machineInfoCollected, + providerID: providerID, + controllerName: ctrl.Name(), + } + + return safe.WriterModify[*infra.Machine](ctx, r, infra.NewMachine(link.Metadata().ID()), func(res *infra.Machine) error { + return helper.modify(ctx, res) + }) +} + +type infraMachineControllerHelper struct { + runtime controller.QRuntime + config *omni.InfraMachineConfig + machineExts *omni.MachineExtensions + link *siderolink.Link + providerID string + controllerName string + machineInfoCollected bool +} + +func (helper *infraMachineControllerHelper) modify(ctx context.Context, infraMachine *infra.Machine) error { + if err := helper.applyInfraMachineConfig(infraMachine, helper.config, helper.machineInfoCollected); err != nil { return err } - infraMachine.Metadata().Labels().Set(omni.LabelInfraProviderID, providerID) + infraMachine.Metadata().Labels().Set(omni.LabelInfraProviderID, helper.providerID) - clusterMachine, err := safe.ReaderGetByID[*omni.ClusterMachine](ctx, r, link.Metadata().ID()) + clusterMachine, err := safe.ReaderGetByID[*omni.ClusterMachine](ctx, helper.runtime, helper.link.Metadata().ID()) if err != nil { if state.IsNotFoundError(err) { return nil @@ -135,7 +248,7 @@ func (h *infraMachineControllerHelper) transformExtraOutput(ctx context.Context, if clusterMachine.Metadata().Phase() == resource.PhaseTearingDown { if clusterMachine.Metadata().Finalizers().Has(ClusterMachineConfigControllerName) { - return xerrors.NewTaggedf[qtransform.SkipReconcileTag]("cluster machine is not reset yet") + return nil // cluster machine is not reset yet } // the machine is deallocated, clear the cluster information and mark it for wipe by assigning it a new wipe ID @@ -147,48 +260,86 @@ func (h *infraMachineControllerHelper) transformExtraOutput(ctx context.Context, infraMachine.TypedSpec().Value.ClusterTalosVersion = "" infraMachine.TypedSpec().Value.Extensions = nil - _, err = helpers.HandleInput[*omni.ClusterMachine](ctx, r, InfraMachineControllerName, link) + _, err = helpers.HandleInput[*omni.ClusterMachine](ctx, helper.runtime, helper.controllerName, helper.link) return err } - if err = r.AddFinalizer(ctx, clusterMachine.Metadata(), InfraMachineControllerName); err != nil { + if err = helper.runtime.AddFinalizer(ctx, clusterMachine.Metadata(), helper.controllerName); err != nil { return err } - talosVersion, extensions, err := h.getClusterInfo(ctx, r, link.Metadata().ID(), machineExts) + schematicConfig, err := safe.ReaderGetByID[*omni.SchematicConfiguration](ctx, helper.runtime, helper.link.Metadata().ID()) if err != nil { + if state.IsNotFoundError(err) { + // the schema configuration is not created yet, skip the cluster information collection + return nil + } + return err } + var extensions []string + + if helper.machineExts != nil { + extensions = helper.machineExts.TypedSpec().Value.Extensions + } + // set the cluster allocation information - infraMachine.TypedSpec().Value.ClusterTalosVersion = talosVersion + infraMachine.TypedSpec().Value.ClusterTalosVersion = schematicConfig.TypedSpec().Value.TalosVersion infraMachine.TypedSpec().Value.Extensions = extensions return nil } -func (h *infraMachineControllerHelper) finalizerRemovalExtraOutput(ctx context.Context, r controller.ReaderWriter, _ *zap.Logger, link *siderolink.Link) error { - if _, err := helpers.HandleInput[*omni.InfraMachineConfig](ctx, r, InfraMachineControllerName, link); err != nil { - return err - } +// MapInput implements the controller.QController interface. +func (ctrl *InfraMachineController) MapInput(ctx context.Context, _ *zap.Logger, runtime controller.QRuntime, ptr resource.Pointer) ([]resource.Pointer, error) { + switch ptr.Type() { + case siderolink.LinkType, + infra.InfraMachineType, + omni.InfraMachineConfigType, + omni.SchematicConfigurationType, + omni.MachineExtensionsType, + omni.ClusterMachineType, + omni.MachineStatusType: + return []resource.Pointer{siderolink.NewLink(resources.DefaultNamespace, ptr.ID(), nil).Metadata()}, nil + case infra.InfraProviderStatusType: + linkList, err := safe.ReaderListAll[*siderolink.Link](ctx, runtime, state.WithLabelQuery(resource.LabelEqual(omni.LabelInfraProviderID, ptr.ID()))) + if err != nil { + return nil, err + } - if _, err := helpers.HandleInput[*omni.MachineExtensions](ctx, r, InfraMachineControllerName, link); err != nil { - return err + ptrSeq := xiter.Map(func(in *siderolink.Link) resource.Pointer { + return in.Metadata() + }, linkList.All()) + + return slices.Collect(ptrSeq), nil } - if _, err := helpers.HandleInput[*omni.ClusterMachine](ctx, r, InfraMachineControllerName, link); err != nil { - return err + return nil, fmt.Errorf("unexpected resource type %q", ptr.Type()) +} + +func (ctrl *InfraMachineController) handleInstallEvent(ctx context.Context, r controller.QRuntime, machineID resource.ID) error { + if _, err := safe.ReaderGetByID[*infra.Machine](ctx, r, machineID); err != nil { + if state.IsNotFoundError(err) { + return nil // if there is no infra machine, there is nothing to do + } } - _, err := helpers.HandleInput[*omni.MachineStatus](ctx, r, InfraMachineControllerName, link) + return safe.WriterModify(ctx, r, infra.NewMachine(machineID), func(machine *infra.Machine) error { + if machine.Metadata().Phase() == resource.PhaseTearingDown { + return nil + } + + machine.TypedSpec().Value.InstallEventId++ - return err + return nil + }) } // applyInfraMachineConfig applies the user-managed configuration from the omni.InfraMachineConfig resource into the infra.Machine. -func (h *infraMachineControllerHelper) applyInfraMachineConfig(infraMachine *infra.Machine, config *omni.InfraMachineConfig, machineInfoCollected bool) error { +func (helper *infraMachineControllerHelper) applyInfraMachineConfig(infraMachine *infra.Machine, config *omni.InfraMachineConfig, machineInfoCollected bool) error { const defaultPreferredPowerState = specs.InfraMachineSpec_POWER_STATE_OFF // todo: introduce a resource to configure this globally or per-provider level // reset the user-override fields except the "Accepted" field @@ -230,25 +381,3 @@ func (h *infraMachineControllerHelper) applyInfraMachineConfig(infraMachine *inf return nil } - -// getClusterInfo returns the Talos version and extensions for the given machine. -// -// At this point, the machine is known to be associated with a cluster. -func (h *infraMachineControllerHelper) getClusterInfo(ctx context.Context, r controller.Reader, id resource.ID, machineExtensions *omni.MachineExtensions) (string, []string, error) { - schematicConfig, err := safe.ReaderGetByID[*omni.SchematicConfiguration](ctx, r, id) - if err != nil { - if state.IsNotFoundError(err) { - return "", nil, xerrors.NewTaggedf[qtransform.SkipReconcileTag]("schema configuration is not created yet") - } - - return "", nil, err - } - - var extensions []string - - if machineExtensions != nil { - extensions = machineExtensions.TypedSpec().Value.Extensions - } - - return schematicConfig.TypedSpec().Value.TalosVersion, extensions, nil -} diff --git a/internal/backend/runtime/omni/controllers/omni/infra_machine_test.go b/internal/backend/runtime/omni/controllers/omni/infra_machine_test.go index 93da6c70b..c98097255 100644 --- a/internal/backend/runtime/omni/controllers/omni/infra_machine_test.go +++ b/internal/backend/runtime/omni/controllers/omni/infra_machine_test.go @@ -8,6 +8,7 @@ package omni_test import ( "testing" + "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/resource/rtestutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -27,7 +28,10 @@ type InfraMachineControllerSuite struct { func (suite *InfraMachineControllerSuite) TestReconcile() { suite.startRuntime() - suite.Require().NoError(suite.runtime.RegisterQController(omnictrl.NewInfraMachineController())) + installEventCh := make(chan resource.ID, 1) + controller := omnictrl.NewInfraMachineController(installEventCh) + + suite.Require().NoError(suite.runtime.RegisterQController(controller)) providerStatus := infra.NewProviderStatus("bare-metal") @@ -52,6 +56,7 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { assertion.Empty(r.TypedSpec().Value.ClusterTalosVersion) assertion.Empty(r.TypedSpec().Value.Extensions) assertion.Empty(r.TypedSpec().Value.WipeId) + assertion.Zero(r.TypedSpec().Value.InstallEventId) }) machineStatus := omni.NewMachineStatus(resources.DefaultNamespace, "machine-1") @@ -60,7 +65,7 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { suite.Require().NoError(suite.state.Create(suite.ctx, machineStatus)) assertResource[*omni.MachineStatus](&suite.OmniSuite, machineStatus.Metadata(), func(r *omni.MachineStatus, assertion *assert.Assertions) { - assertion.True(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.True(r.Metadata().Finalizers().Has(controller.Name())) }) assertResource[*infra.Machine](&suite.OmniSuite, infraMachineMD, func(r *infra.Machine, assertion *assert.Assertions) { @@ -76,7 +81,7 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { suite.Require().NoError(suite.state.Create(suite.ctx, config)) assertResource[*omni.InfraMachineConfig](&suite.OmniSuite, config.Metadata(), func(r *omni.InfraMachineConfig, assertion *assert.Assertions) { - assertion.True(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.True(r.Metadata().Finalizers().Has(controller.Name())) }) assertResource[*infra.Machine](&suite.OmniSuite, infraMachineMD, func(r *infra.Machine, assertion *assert.Assertions) { @@ -92,7 +97,7 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { // assert that the finalizer is added assertResource[*omni.ClusterMachine](&suite.OmniSuite, clusterMachine.Metadata(), func(r *omni.ClusterMachine, assertion *assert.Assertions) { - assertion.True(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.True(r.Metadata().Finalizers().Has(controller.Name())) }) // create schematic configuration @@ -115,7 +120,7 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { suite.Require().NoError(suite.state.Create(suite.ctx, extensions)) assertResource[*omni.MachineExtensions](&suite.OmniSuite, extensions.Metadata(), func(r *omni.MachineExtensions, assertion *assert.Assertions) { - assertion.True(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.True(r.Metadata().Finalizers().Has(controller.Name())) }) // assert that the cluster machine has the correct extensions @@ -128,21 +133,37 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { // assert that the finalizer is removed, cluster related fields are cleared, and a new wipe ID is generated assertResource[*infra.Machine](&suite.OmniSuite, infraMachineMD, func(r *infra.Machine, assertion *assert.Assertions) { - assertion.False(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.False(r.Metadata().Finalizers().Has(controller.Name())) assertion.Empty(r.TypedSpec().Value.ClusterTalosVersion) assertion.Empty(r.TypedSpec().Value.Extensions) assertion.NotEmpty(r.TypedSpec().Value.WipeId) }) - // test finalizer removal + installEventCh <- infraMachineMD.ID() + + // assert that install id is incremented + + assertResource[*infra.Machine](&suite.OmniSuite, infraMachineMD, func(r *infra.Machine, assertion *assert.Assertions) { + assertion.Equal(uint64(1), r.TypedSpec().Value.InstallEventId) + }) + + installEventCh <- infraMachineMD.ID() + + // assert that install id is incremented again + + assertResource[*infra.Machine](&suite.OmniSuite, infraMachineMD, func(r *infra.Machine, assertion *assert.Assertions) { + assertion.Equal(uint64(2), r.TypedSpec().Value.InstallEventId) + }) + + // test finalizers // reallocate the machine to a cluster suite.Require().NoError(suite.state.Create(suite.ctx, clusterMachine)) // assert that the finalizer is added assertResource[*omni.ClusterMachine](&suite.OmniSuite, clusterMachine.Metadata(), func(r *omni.ClusterMachine, assertion *assert.Assertions) { - assertion.True(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.True(r.Metadata().Finalizers().Has(controller.Name())) }) // destroy the link @@ -150,19 +171,19 @@ func (suite *InfraMachineControllerSuite) TestReconcile() { // assert that the finalizers are removed assertResource[*omni.ClusterMachine](&suite.OmniSuite, infraMachineMD, func(r *omni.ClusterMachine, assertion *assert.Assertions) { - assertion.False(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.False(r.Metadata().Finalizers().Has(controller.Name())) }) assertResource[*omni.InfraMachineConfig](&suite.OmniSuite, infraMachineMD, func(r *omni.InfraMachineConfig, assertion *assert.Assertions) { - assertion.False(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.False(r.Metadata().Finalizers().Has(controller.Name())) }) assertResource[*omni.MachineExtensions](&suite.OmniSuite, infraMachineMD, func(r *omni.MachineExtensions, assertion *assert.Assertions) { - assertion.False(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.False(r.Metadata().Finalizers().Has(controller.Name())) }) assertResource[*omni.MachineStatus](&suite.OmniSuite, infraMachineMD, func(r *omni.MachineStatus, assertion *assert.Assertions) { - assertion.False(r.Metadata().Finalizers().Has(omnictrl.InfraMachineControllerName)) + assertion.False(r.Metadata().Finalizers().Has(controller.Name())) }) // assert that infra.Machine is removed diff --git a/internal/backend/runtime/omni/infraprovider/state.go b/internal/backend/runtime/omni/infraprovider/state.go index 074c960d9..165f53f60 100644 --- a/internal/backend/runtime/omni/infraprovider/state.go +++ b/internal/backend/runtime/omni/infraprovider/state.go @@ -423,8 +423,8 @@ func (st *State) checkNamespaceAndType(ns resource.Namespace, infraProviderID st return false, nil } - return false, status.Errorf(codes.PermissionDenied, "namespace not allowed, must be one of %s or %s", - resources.InfraProviderNamespace, infraProviderSpecificNamespace) + return false, status.Errorf(codes.PermissionDenied, "namespace not allowed, must be one of: %s, %s, %s", + resources.InfraProviderNamespace, resources.InfraProviderEphemeralNamespace, infraProviderSpecificNamespace) } // resourceConfig defines the resource-specific configuration to be validated by the state. @@ -444,7 +444,7 @@ func IsInfraProviderResource(ns resource.Namespace, resType resource.Type) bool // getResourceConfig returns the configuration for the given resource type. func getResourceConfig(ns resource.Namespace, resType resource.Type) (config resourceConfig, isInfraProviderResource bool) { if strings.HasPrefix(ns, resources.InfraProviderSpecificNamespacePrefix) { - return config, true + return resourceConfig{}, true } switch resType { @@ -458,8 +458,6 @@ func getResourceConfig(ns resource.Namespace, resType resource.Type) (config res return resourceConfig{ readOnlyForProviders: true, }, true - case infra.InfraMachineStateType: - return resourceConfig{}, true case infra.InfraMachineStatusType: return resourceConfig{}, true case infra.InfraProviderStatusType: diff --git a/internal/backend/runtime/omni/infraprovider/state_test.go b/internal/backend/runtime/omni/infraprovider/state_test.go index 5ffe220cc..3ad4a9d78 100644 --- a/internal/backend/runtime/omni/infraprovider/state_test.go +++ b/internal/backend/runtime/omni/infraprovider/state_test.go @@ -244,6 +244,7 @@ func TestInfraProviderSpecificNamespace(t *testing.T) { res1 := newTestRes(infraProviderResNamespace, "test-res-1", testResSpec{str: "foo"}) + require.True(t, infraprovider.IsInfraProviderResource(infraProviderResNamespace, res1.Metadata().Type())) require.NoError(t, st.Create(ctx, res1)) _, err := safe.StateUpdateWithConflicts(ctx, st, res1.Metadata(), func(res *testRes) error { diff --git a/internal/backend/runtime/omni/omni.go b/internal/backend/runtime/omni/omni.go index 59f507e98..93040348b 100644 --- a/internal/backend/runtime/omni/omni.go +++ b/internal/backend/runtime/omni/omni.go @@ -86,7 +86,7 @@ type Runtime struct { //nolint:maintidx func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workloadProxyReconciler *workloadproxy.Reconciler, resourceLogger *resourcelogger.Logger, imageFactoryClient *imagefactory.Client, linkCounterDeltaCh <-chan siderolink.LinkCounterDeltas, - siderolinkEventsCh <-chan *omni.MachineStatusSnapshot, resourceState state.State, virtualState *virtual.State, metricsRegistry prometheus.Registerer, + siderolinkEventsCh <-chan *omni.MachineStatusSnapshot, installEventCh <-chan cosiresource.ID, resourceState state.State, virtualState *virtual.State, metricsRegistry prometheus.Registerer, defaultDiscoveryClient, embeddedDiscoveryClient omnictrl.DiscoveryClient, logger *zap.Logger, ) (*Runtime, error) { var opts []options.Option @@ -271,7 +271,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl omnictrl.NewMachineRequestSetStatusController(), omnictrl.NewClusterMachineRequestStatusController(), omnictrl.NewMachineTeardownController(), - omnictrl.NewInfraMachineController(), + omnictrl.NewInfraMachineController(installEventCh), omnictrl.NewInfraProviderConfigPatchController(), } diff --git a/internal/backend/runtime/omni/omni_test.go b/internal/backend/runtime/omni/omni_test.go index 5ecf083ab..e98f0c7ba 100644 --- a/internal/backend/runtime/omni/omni_test.go +++ b/internal/backend/runtime/omni/omni_test.go @@ -84,7 +84,7 @@ func (suite *OmniRuntimeSuite) SetupTest() { discoveryServiceClient := &discoveryClientMock{} workloadProxyReconciler := workloadproxy.NewReconciler(logger, zapcore.InfoLevel) - suite.runtime, err = omniruntime.New(clientFactory, dnsService, workloadProxyReconciler, nil, nil, nil, nil, + suite.runtime, err = omniruntime.New(clientFactory, dnsService, workloadProxyReconciler, nil, nil, nil, nil, nil, resourceState, nil, prometheus.NewRegistry(), discoveryServiceClient, nil, logger) suite.Require().NoError(err) diff --git a/internal/backend/runtime/omni/talosconfig_test.go b/internal/backend/runtime/omni/talosconfig_test.go index 564af64b2..db7cd60d0 100644 --- a/internal/backend/runtime/omni/talosconfig_test.go +++ b/internal/backend/runtime/omni/talosconfig_test.go @@ -41,7 +41,7 @@ func TestOperatorTalosconfig(t *testing.T) { discoveryServiceClient := &discoveryClientMock{} workloadProxyReconciler := workloadproxy.NewReconciler(logger, zapcore.InfoLevel) - r, err := omniruntime.New(clientFactory, dnsService, workloadProxyReconciler, nil, nil, nil, nil, + r, err := omniruntime.New(clientFactory, dnsService, workloadProxyReconciler, nil, nil, nil, nil, nil, st, nil, prometheus.NewRegistry(), discoveryServiceClient, nil, logger) require.NoError(t, err) diff --git a/internal/backend/server.go b/internal/backend/server.go index 2c25c284c..dfff5fc24 100644 --- a/internal/backend/server.go +++ b/internal/backend/server.go @@ -114,6 +114,7 @@ type Server struct { linkCounterDeltaCh chan<- siderolink.LinkCounterDeltas siderolinkEventsCh chan<- *omnires.MachineStatusSnapshot + installEventCh chan<- resource.ID proxyServer Proxy bindAddress string @@ -132,6 +133,7 @@ func NewServer( imageFactoryClient *imagefactory.Client, linkCounterDeltaCh chan<- siderolink.LinkCounterDeltas, siderolinkEventsCh chan<- *omnires.MachineStatusSnapshot, + installEventCh chan<- resource.ID, omniRuntime *omni.Runtime, talosRuntime *talos.Runtime, logHandler *siderolink.LogHandler, @@ -152,6 +154,7 @@ func NewServer( auditor: auditor, linkCounterDeltaCh: linkCounterDeltaCh, siderolinkEventsCh: siderolinkEventsCh, + installEventCh: installEventCh, proxyServer: proxyServer, bindAddress: bindAddress, metricsBindAddress: metricsBindAddress, @@ -555,7 +558,7 @@ func (s *Server) runMachineAPI(ctx context.Context) error { } omniState := s.omniRuntime.State() - machineEventHandler := machineevent.NewHandler(omniState, s.logger, s.siderolinkEventsCh) + machineEventHandler := machineevent.NewHandler(omniState, s.logger, s.siderolinkEventsCh, s.installEventCh) slink, err := siderolink.NewManager( ctx, diff --git a/internal/pkg/machineevent/handler.go b/internal/pkg/machineevent/handler.go index 5ec0ed59e..6151c730d 100644 --- a/internal/pkg/machineevent/handler.go +++ b/internal/pkg/machineevent/handler.go @@ -21,25 +21,25 @@ import ( "go.uber.org/zap" "github.com/siderolabs/omni/client/pkg/omni/resources" - "github.com/siderolabs/omni/client/pkg/omni/resources/infra" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" - "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/helpers" "github.com/siderolabs/omni/internal/pkg/auth/actor" ) // Handler is a machine event handler. type Handler struct { - logger *zap.Logger - state state.State - notifyCh chan<- *omni.MachineStatusSnapshot + logger *zap.Logger + state state.State + notifyCh chan<- *omni.MachineStatusSnapshot + installEventCh chan<- resource.ID } // NewHandler creates a new machine event handler. -func NewHandler(state state.State, logger *zap.Logger, notifyCh chan<- *omni.MachineStatusSnapshot) *Handler { +func NewHandler(state state.State, logger *zap.Logger, notifyCh chan<- *omni.MachineStatusSnapshot, installEventCh chan<- resource.ID) *Handler { return &Handler{ - state: state, - logger: logger, - notifyCh: notifyCh, + state: state, + logger: logger, + notifyCh: notifyCh, + installEventCh: installEventCh, } } @@ -115,66 +115,17 @@ func (handler *Handler) handleSequenceEvent(ctx context.Context, event *machinea setInstalled = true } - // check if there is an infra machine for the machine - infraMachine, err := safe.StateGetByID[*infra.Machine](ctx, handler.state, machineID) - if err != nil { - if state.IsNotFoundError(err) { - logger.Debug("no matching infra.Machine found for machine, remove machine state if it exists", zap.String("machine", machineID)) - - return handler.removeMachineState(ctx, machineID) - } - - return err - } - if !setInstalled { return nil } - modify := func(res *infra.MachineState) error { - helpers.CopyAllLabels(infraMachine, res) - helpers.CopyAllAnnotations(infraMachine, res) - - res.TypedSpec().Value.Installed = true - - return nil - } - - machineState := infra.NewMachineState(machineID) - - if err = modify(machineState); err != nil { - return err - } - - err = handler.state.Create(ctx, machineState) - if err != nil { - if !state.IsConflictError(err) { - return err - } - - if _, err = safe.StateUpdateWithConflicts(ctx, handler.state, machineState.Metadata(), modify); err != nil { - return err - } + select { + case <-ctx.Done(): + return ctx.Err() + case handler.installEventCh <- machineID: } - logger.Info("marked infra machine as installed") - - return nil -} - -func (handler *Handler) removeMachineState(ctx context.Context, machineID resource.ID) error { - md := infra.NewMachineState(machineID).Metadata() - - destroyReady, err := handler.state.Teardown(ctx, md) - if err != nil && !state.IsNotFoundError(err) { - return err - } - - if destroyReady { - if err = handler.state.Destroy(ctx, md); err != nil && !state.IsNotFoundError(err) { - return err - } - } + logger.Info("sent machine installed event", zap.String("id", machineID)) return nil } diff --git a/internal/pkg/machineevent/handler_test.go b/internal/pkg/machineevent/handler_test.go index 7b667bb8e..06f56ea9c 100644 --- a/internal/pkg/machineevent/handler_test.go +++ b/internal/pkg/machineevent/handler_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/state" "github.com/cosi-project/runtime/pkg/state/impl/inmem" "github.com/cosi-project/runtime/pkg/state/impl/namespaced" @@ -30,7 +30,8 @@ import ( func TestSequenceEvent(t *testing.T) { st := state.WrapCore(namespaced.NewState(inmem.Build)) logger := zaptest.NewLogger(t) - handler := machineevent.NewHandler(st, logger, nil) + installEventCh := make(chan resource.ID, 1) + handler := machineevent.NewHandler(st, logger, nil, installEventCh) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) @@ -52,7 +53,7 @@ func TestSequenceEvent(t *testing.T) { require.NoError(t, handler.HandleEvent(ctx, event)) - assertInfraMachineState(ctx, t, st, false, false) + assert.Len(t, installEventCh, 0) // assert installed condition 1 @@ -67,9 +68,14 @@ func TestSequenceEvent(t *testing.T) { require.NoError(t, handler.HandleEvent(ctx, event)) - assertInfraMachineState(ctx, t, st, true, true) + assert.Len(t, installEventCh, 1) - require.NoError(t, st.Destroy(ctx, infra.NewMachineState("test-machine").Metadata())) + select { + case <-ctx.Done(): + require.Fail(t, "timeout") + case installEvent := <-installEventCh: + assert.Equal(t, testMachine.Metadata().ID(), installEvent) + } // assert installed condition 2 @@ -80,34 +86,11 @@ func TestSequenceEvent(t *testing.T) { require.NoError(t, handler.HandleEvent(ctx, event)) - assertInfraMachineState(ctx, t, st, true, true) - - // remove the infra machine, assert that state is removed + assert.Len(t, installEventCh, 1) - require.NoError(t, st.Destroy(ctx, infraMachine.Metadata())) - - event.Payload = &machine.SequenceEvent{ - Sequence: "something", + select { + case <-ctx.Done(): + require.Fail(t, "timeout") + case <-installEventCh: } - - require.NoError(t, handler.HandleEvent(ctx, event)) - - assertInfraMachineState(ctx, t, st, false, false) -} - -func assertInfraMachineState(ctx context.Context, t *testing.T, st state.State, exists, installed bool) { - machineState, err := safe.StateGetByID[*infra.MachineState](ctx, st, "test-machine") - if err != nil { - if !state.IsNotFoundError(err) { - require.NoError(t, err) - } - - if exists { - require.Fail(t, "machine state not found") - } - - return - } - - assert.Equal(t, installed, machineState.TypedSpec().Value.Installed) } diff --git a/internal/pkg/siderolink/siderolink_test.go b/internal/pkg/siderolink/siderolink_test.go index 24a5ea10c..ed69c3679 100644 --- a/internal/pkg/siderolink/siderolink_test.go +++ b/internal/pkg/siderolink/siderolink_test.go @@ -115,7 +115,7 @@ func (suite *SiderolinkSuite) SetupTest() { var err error - eventHandler := machineevent.NewHandler(suite.state, zaptest.NewLogger(suite.T()), make(chan *omni.MachineStatusSnapshot)) + eventHandler := machineevent.NewHandler(suite.state, zaptest.NewLogger(suite.T()), make(chan *omni.MachineStatusSnapshot), nil) suite.manager, err = sideromanager.NewManager(suite.ctx, suite.state, &fakeWireguardHandler{}, params, zaptest.NewLogger(suite.T()), nil, eventHandler, nil) suite.Require().NoError(err)