diff --git a/Makefile b/Makefile index d8a729174..efe836a21 100644 --- a/Makefile +++ b/Makefile @@ -632,6 +632,7 @@ GO_GENERATE_PACKAGES := \ ./submitqueue/extension/changeprovider/... \ ./submitqueue/extension/conflict/... \ ./submitqueue/extension/queueconfig/... \ + ./submitqueue/extension/queuepause/... \ ./submitqueue/extension/speculation/... \ ./submitqueue/extension/storage/... \ ./submitqueue/extension/validator/... \ diff --git a/submitqueue/entity/BUILD.bazel b/submitqueue/entity/BUILD.bazel index d3fcf8eb0..6e1561ab1 100644 --- a/submitqueue/entity/BUILD.bazel +++ b/submitqueue/entity/BUILD.bazel @@ -16,6 +16,7 @@ go_library( "path_build.go", "queue_batch_state.go", "queue_config.go", + "queue_pause.go", "request.go", "request_batch.go", "request_history.go", diff --git a/submitqueue/entity/queue_pause.go b/submitqueue/entity/queue_pause.go new file mode 100644 index 000000000..ca1264378 --- /dev/null +++ b/submitqueue/entity/queue_pause.go @@ -0,0 +1,29 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entity + +// QueuePause is a point-in-time snapshot of the pause switches of a single +// queue. The two switches are independent, and the zero value means the queue +// is not paused. +type QueuePause struct { + // RejectLands is true while the queue refuses new land requests. + RejectLands bool + + // FreezeProcessing is true while the queue's accepted requests are not advanced. + FreezeProcessing bool + + // Reason is the operator's free-text explanation for the pause. Empty when none was given. + Reason string +} diff --git a/submitqueue/extension/queuepause/BUILD.bazel b/submitqueue/extension/queuepause/BUILD.bazel new file mode 100644 index 000000000..6aa6ee319 --- /dev/null +++ b/submitqueue/extension/queuepause/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["queuepause.go"], + importpath = "github.com/uber/submitqueue/submitqueue/extension/queuepause", + visibility = ["//visibility:public"], + deps = ["//submitqueue/entity:go_default_library"], +) diff --git a/submitqueue/extension/queuepause/README.md b/submitqueue/extension/queuepause/README.md new file mode 100644 index 000000000..61b714d78 --- /dev/null +++ b/submitqueue/extension/queuepause/README.md @@ -0,0 +1,15 @@ +# Queue Pause Extension + +Vendor-agnostic source of per-queue pause state. See [the pause and unpause RFC](../../../doc/rfc/submitqueue/queue-pause-and-unpause.md) for the design. + +## Interfaces + +### Store + +Returns the pause state of a queue by name. The state is two independent switches: reject new land requests, and freeze processing of accepted requests. A queue with no entry is not paused, and an error means the backend could not be read. + +The store only reports state. The gateway and orchestrator enforce it, so every deployment runs the same rejection and freezing logic and only the source of the state differs. + +## Entities + +`QueuePause` lives in `submitqueue/entity/queue_pause.go`. diff --git a/submitqueue/extension/queuepause/mock/BUILD.bazel b/submitqueue/extension/queuepause/mock/BUILD.bazel new file mode 100644 index 000000000..08356e56a --- /dev/null +++ b/submitqueue/extension/queuepause/mock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["queuepause_mock.go"], + importpath = "github.com/uber/submitqueue/submitqueue/extension/queuepause/mock", + visibility = ["//visibility:public"], + deps = [ + "//submitqueue/entity:go_default_library", + "@org_uber_go_mock//gomock:go_default_library", + ], +) diff --git a/submitqueue/extension/queuepause/mock/queuepause_mock.go b/submitqueue/extension/queuepause/mock/queuepause_mock.go new file mode 100644 index 000000000..90a0b682f --- /dev/null +++ b/submitqueue/extension/queuepause/mock/queuepause_mock.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: queuepause.go +// +// Generated by this command: +// +// mockgen -source=queuepause.go -destination=mock/queuepause_mock.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + entity "github.com/uber/submitqueue/submitqueue/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockStore is a mock of Store interface. +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder + isgomock struct{} +} + +// MockStoreMockRecorder is the mock recorder for MockStore. +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance. +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockStore) Get(ctx context.Context, queue string) (entity.QueuePause, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, queue) + ret0, _ := ret[0].(entity.QueuePause) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockStoreMockRecorder) Get(ctx, queue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStore)(nil).Get), ctx, queue) +} diff --git a/submitqueue/extension/queuepause/queuepause.go b/submitqueue/extension/queuepause/queuepause.go new file mode 100644 index 000000000..d4f2b17b8 --- /dev/null +++ b/submitqueue/extension/queuepause/queuepause.go @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuepause + +//go:generate mockgen -source=queuepause.go -destination=mock/queuepause_mock.go -package=mock + +import ( + "context" + + "github.com/uber/submitqueue/submitqueue/entity" +) + +// Store is the source of per-queue pause state. It reports the switches and +// decides nothing; enforcement lives in the services that resolve it. +type Store interface { + // Get returns the current pause state of a queue. A queue with no entry + // returns the zero state (not paused) and no error. A non-nil error means + // the backend could not be read, not that the queue is unpaused; a caller + // that enforces a switch must not fail open on it. Implementations must be + // safe for concurrent use. + Get(ctx context.Context, queue string) (entity.QueuePause, error) +}