merge: sync Rongsong's scheduler-framework (c5b172c7..3442a57d, 781 commits)

Merge origin/scheduler-framework into merge-sf-20260903 (97 commits on
ours: bug-1490/1494/1498/1509/1513/1521/1522 fixes, PEND-reason P1,
scheduler-agent Phase 2, bug-924 owner-pid shim).

Bookkeeping excluded per policy: .kiro/, .wolf/, CHANGES.md, reports/
(92 files dropped); .claude/ + CLAUDE.md keep ours.

Key theirs-side changes absorbed:
- bug-1938/1960: QUEUE_STAT_CLOSED no longer gates dispatch (closed
  queue refuses new submissions only; pending jobs still run) --
  supersedes our QUEUE_CLOSED preason on the dispatch path;
  sched_queue_status_reason drops that branch
- bug-1846: config push now stashes and applies between scheduling
  rounds (sched_stash_config/sched_apply_pending_config), applied at
  the same main-thread round boundary that preserves bug-1513
  serialization
- bug-1760: sched_check_pending_rejoin exposed +
  xcecli_is_connection_broken public predicate
- bug-1936: execuser/exechome/execcwd/exdata carried on submission
  (superset of our bug-1500 execuser fallback; our job-typed
  resolveExecUser removed as dead code)
- bug-1945~1962: apiserver auth hardening (HS256 pinning, default-
  secret rejection, logout revocation, bcrypt, RBAC) -- our API-key
  branch folded into controllers.ParseTokenClaims so both coexist
- bug-1736: rawRunUsage job run-usage fields in the Go model
- xce_test relocated src/controller/testsuite/test.c ->
  src/harness/test.c; controller suite split into
  controller_test.c + fixtures; their clitest fixtures spawn xce_test
  mid-suite under `meson test -j`

Ours preserved across the merge:
- PEND-reason P1 (codes 1-9, preason.h, job_preason machinery) --
  gate re-ported into their relocated allocator (sched_election.c)
  after plugin_job_election, with attach on the allocator thread
  only (bug-1521 race discipline)
- PEND-reason P0 main_reason semantics RECONCILED to theirs: the
  merge initially re-carried our first-detail-add-promotes-main_reason
  fallback in add_job_preason (4e2ac86), but their whole trace/verify
  stack treats "detail-only => main_reason 0" as a contract --
  sched_trace_capture counts main_reason as its own occurrence
  ("Expected 2 Was 3" in test_trace_capture), the outcome renderer
  prints "main reason: none" for detail-only PENDs (test_repl_trace_e2e),
  and the committed .replay/.sim_verify baselines encode main_reason=0
  (test_repl_trace_baseline byte compare, test_trace_equivalence
  off-vs-traced). The fallback is dropped from the shared lib;
  the user-visible "overall reason" survives as an API-layer view:
  rawJobToModel (convert_pure.go) promotes the first detail row to
  main_reason_name when main_reason is 0 (main_reason VALUE marshals
  unchanged). add_job_preason keeps only the NULL-reasons guard
- bug-1498: sched_job_is_ready rejects non-PEND/suspended jobs
- bug-1490/1781: xce_jobctrl_t.action as capnp union discriminant
  (their jobCtrlUnionDiscriminant, same semantics)
- xcecli typed PendingReason field (53c00834), bug-1522 reconnect
  resend, xcecli_force_reconnect + their is_connection_broken both
  exported in client.h/libclient
- t1/t2/t3 test-node hostconfig in src/configs/hosts.yaml (bug-1494),
  /xce/v1/events + /xce/v1/apikeys whitelist routes (agent Phase 2)
- lib ASAN_OPTIONS=detect_leaks=0 test env + their oom-test block
- drop-audit: our bug-1504 fix covered by their bug-1680 (no action)

bug-924 follow-up (replaces the 67f3f6ad scratch-dir wipe): the
take-theirs audit's one "drop" was re-evaluated and REVERTED instead of
re-applied -- a global `find /tmp -name 'xce_*' -exec rm -rf` at suite
start/exit races the LIVE /tmp/xce_* fixtures of sibling suites under
`meson test -j` (harness_unit_test's clitest fixtures spawn xce_test as
a child mid-suite; the wipe destroyed the parent's fixtures ->
test_test_cli failures). Coverage instead:
- mkdtemp_clean shim gains mkdtemp_clean_mark/sweep_since
  (testsuite_support/mkdtemp_clean.[ch])
- new case_scratch_sweep.[ch]: aggregate runners (controller_test.c,
  harness_test.c -- hundreds of cases per process) call
  case_scratch_sweep_begin/_end_ok from setUp/tearDown; a PASSED case's
  dirs are swept immediately in tearDown (after engine reaping), FAILED
  cases keep their dirs for post-mortem; XCE_MKDTEMP_KEEP_ALL=1
  disables. One full controller_unit_test run was observed to leak 12G+
  to ENOSPC without this, cascading into unrelated "Expected Non-NULL"
  failures
- harness_unit_test_deps links the shim library as well

Test reconciliations on adopt:
- core-ready-gates: sched_queue_is_ready now gates only
  INACT_ADMIN|INACT_WINDOW (bug-1938/1960); CLOSED queues elect --
  covered end-to-end by test_queue_admin_control.c
- test_worker_resync: the 4 restart-replay RUN assertions predate the
  bug-1509 B3 marking (8858afed) landing on the same lineage; replayed
  RUN jobs now assert RUN|UNKWN|TRANSIENT (TWR_ASSERT_REPLAYED_RUN);
  terminal DONE/EXIT assertions unchanged
- convert_pure.go: our job-typed resolveExecUser deleted (superseded
  by their bug-1780/1936 two-arg version)
- test_preason: the 3 P0-promotion assertions reverted to the
  detail-keeps-main-0 contract; new tests cover limit-code detail
  rows and explicit-main-wins-over-later-details; apiserver
  convert_test.go gains the detail-only name-promotion cases

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
lei.chen 2026-10-14 05:17:03 +00:00
commit 0c8607345e
663 changed files with 139848 additions and 13614 deletions

View file

@ -7,46 +7,46 @@ import (
func TestClusterInfo_NewFields(t *testing.T) {
tests := []struct {
name string
name string
cluster ClusterInfo
field string
field string
want interface{}
}{
{
name: "HeartbeatInterval field exists and serializes",
name: "HeartbeatInterval field exists and serializes",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 30},
field: "heartbeat_interval",
want: float64(30),
field: "heartbeat_interval",
want: float64(30),
},
{
name: "Vcpus field exists and serializes",
name: "Vcpus field exists and serializes",
cluster: ClusterInfo{ClusterName: "test", Vcpus: 16},
field: "vcpus",
want: float64(16),
field: "vcpus",
want: float64(16),
},
{
name: "InactiveHeartbeatCount field exists and serializes",
name: "InactiveHeartbeatCount field exists and serializes",
cluster: ClusterInfo{ClusterName: "test", InactiveHeartbeatCount: 3},
field: "inactive_heartbeat_count",
want: float64(3),
field: "inactive_heartbeat_count",
want: float64(3),
},
{
name: "HeartbeatInterval zero value serializes as 0 not omitted",
name: "HeartbeatInterval zero value serializes as 0 not omitted",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0},
field: "heartbeat_interval",
want: float64(0),
field: "heartbeat_interval",
want: float64(0),
},
{
name: "Vcpus zero value serializes as 0 not omitted",
name: "Vcpus zero value serializes as 0 not omitted",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0},
field: "vcpus",
want: float64(0),
field: "vcpus",
want: float64(0),
},
{
name: "InactiveHeartbeatCount zero value serializes as 0 not omitted",
name: "InactiveHeartbeatCount zero value serializes as 0 not omitted",
cluster: ClusterInfo{ClusterName: "test", HeartbeatInterval: 0, Vcpus: 0, InactiveHeartbeatCount: 0},
field: "inactive_heartbeat_count",
want: float64(0),
field: "inactive_heartbeat_count",
want: float64(0),
},
}
@ -122,4 +122,4 @@ func mapKeys(m map[string]interface{}) []string {
keys = append(keys, k)
}
return keys
}
}

View file

@ -1,17 +1,17 @@
package models
const (
CONTROL_JOB = 1
CONTROL_HOST = 2
CONTROL_QUEUE = 3
CONTROL_CLUSTER = 4
CONTROL_DEBUG = 5
)
type ActionControl struct {
TargetType int `json:"target_type"`
ActionType int `json:"action_type"`
Content Jsonb `json:"content"`
SubmitTime int64 `json:"submit_time"`
Submitter string `json:"submitter"`
}
package models
const (
CONTROL_JOB = 1
CONTROL_HOST = 2
CONTROL_QUEUE = 3
CONTROL_CLUSTER = 4
CONTROL_DEBUG = 5
)
type ActionControl struct {
TargetType int `json:"target_type"`
ActionType int `json:"action_type"`
Content Jsonb `json:"content"`
SubmitTime int64 `json:"submit_time"`
Submitter string `json:"submitter"`
}

View file

@ -88,9 +88,9 @@ type HostInfo struct {
NumSSusp int `json:"num_ssusp"`
NumUSusp int `json:"num_ususp"`
//NumRsv int `json:"num_rsv"`
NumDone int `json:"num_done"`
Topology string `json:"topology"` // JSONB
SchedStop string `json:"sched_stop"` // JSONB
NumDone int `json:"num_done"`
Topology string `json:"topology"` // JSONB
SchedStop string `json:"sched_stop"` // JSONB
ResourceValues []ResourceDistributionInfo `json:"resource_values"` // per-worker resource values
//RealLoad string `json:"real_load"` // JSONB
}

View file

@ -68,4 +68,4 @@ func TestHostStateStr(t *testing.T) {
}
})
}
}
}

View file

@ -125,9 +125,12 @@ var JobKillSignalControlMap = map[int][]int{
JOB_KILL: {JOBSTAT["RUN"], JOBSTAT["PEND"], JOBSTAT["SSUSP"], JOBSTAT["USUSP"], JOBSTAT["PSUSP"]},
}
// DefaultActionSignal names each action's default signal. bug-1957: kill and
// stop name the last signal of their graceful sequences (backend
// jobCtrlParams sends them with the configured grace period).
var DefaultActionSignal = map[string]string{
"kill": "TERM",
"stop": "TSTP",
"kill": "KILL",
"stop": "STOP",
"resume": "CONT",
}
@ -175,8 +178,8 @@ var JOB_SUPPORT_FILTERS = []string{
"job_id",
//"user_group",
//"job_group",
//"project_name",
//"license_project",
"project_name", // bug-1935: xcecli jobs list -P
"license_project", // bug-1935: xcecli jobs list --license-project
//"sla",
"submit_time",
"queue_time",
@ -485,6 +488,11 @@ var JobFieldMappings = map[string]string{
}
type JobInfo struct {
// Provisional marks a record the apiserver stored itself when the
// controller accepted the submit, before the controller's own
// OnJobCreated/array push arrived (bug-1894); that push replaces it.
// Not a column: json "-" keeps it out of query results.
Provisional bool `json:"-"`
Unique_JobID int64 `json:"unique_job_id"`
JobID int64 `json:"job_id"`
ArrayIdx int `json:"array_idx"` //new
@ -542,18 +550,29 @@ type JobInfo struct {
ReserveTime int64 `json:"reserve_time"`
Duration int `json:"duration"`
CPUTime int `json:"cpu_time"`
ExitStatus Jsonb `json:"exit_status"` //change type, JSONB as []byte
ExecHosts Jsonb `json:"exec_hosts"` //change type, JSONB as []byte
ExecUID int `json:"exec_uid"`
ExecHome string `json:"exec_home"`
ExecCWD string `json:"exec_cwd"`
ExecUser string `json:"exec_user"` //name change
ChargedSaap string `json:"charged_saap"`
RunTime int `json:"run_time"`
RunTimeEstimation int `json:"run_time_estimation"`
PendingReason Jsonb `json:"pending_reason"` //change type, JSONB as []byte
PIDInfo Jsonb `json:"pid_info"` // JSONB as []byte
JobRusage Jsonb `json:"job_rusage"` // JSONB as []byte
// Run usage (bug-1736), from the controller's accumulated job usage with
// LSF bjobs -l definitions; zero until the first usage report.
MaxMem float64 `json:"max_mem"` // MB
AvgMem float64 `json:"avg_mem"` // MB
UsedMem float64 `json:"used_mem"` // MB, latest
PeakCPU float64 `json:"peak_cpu"` // highest cores used at once
PeakCPUTime int `json:"peak_cpu_time"` // peak_cpu rounded
PeakCPUDuration float64 `json:"peak_cpu_duration"` // seconds, the interval that set the peak
MemEfficiency float64 `json:"mem_efficiency"` // percent of the rusage[mem] request
AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // percent, cpu_time / elapsed / slots
PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // percent, peak_cpu / slots
ExitStatus Jsonb `json:"exit_status"` //change type, JSONB as []byte
ExecHosts Jsonb `json:"exec_hosts"` //change type, JSONB as []byte
ExecUID int `json:"exec_uid"`
ExecHome string `json:"exec_home"`
ExecCWD string `json:"exec_cwd"`
ExecUser string `json:"exec_user"` //name change
ChargedSaap string `json:"charged_saap"`
RunTime int `json:"run_time"`
RunTimeEstimation int `json:"run_time_estimation"`
PendingReason Jsonb `json:"pending_reason"` //change type, JSONB as []byte
PIDInfo Jsonb `json:"pid_info"` // JSONB as []byte
JobRusage Jsonb `json:"job_rusage"` // JSONB as []byte
}
type JobExecHosts struct {
@ -587,32 +606,25 @@ type ArrayJobInfo struct {
}
type JobAdditionalFields struct {
JobID int64 `json:"job_id"`
SubmitUser string `json:"submit_user"`
JobStatus int `json:"job_status"`
JobID int64 `json:"job_id"`
SubmitUser string `json:"submit_user"`
JobStatus int `json:"job_status"`
SubmitTime int64 `json:"submit_time"`
FromHost string `json:"from_host"`
ExecHosts string `json:"exec_hosts"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
JobRusage Jsonb `json:"job_rusage"`
MaxMem float64 `json:"max_mem"`
AvgMem float64 `json:"avg_mem"`
UsedMem float64 `json:"used_mem"`
CPUTime int `json:"cpu_time"`
PeakCPUTime int `json:"peak_cpu_time"`
PeakCPUDuration float64 `json:"peak_cpu_duration"`
MemEfficiency float64 `json:"mem_efficiency"` // Memory efficiency in percentage
AvgCPUEfficiency float64 `json:"avg_cpu_efficiency"` // Average CPU efficiency in percentage
PeakCPUEfficiency float64 `json:"peak_cpu_efficiency"` // Peak CPU efficiency in percentage
NumHosts int `json:"num_hosts"` // Number of hosts used by the job
FirstHost string `json:"first_host"` // First host where the job ran
AllocSlot string `json:"alloc_slots"` // Allocated slot information
PIDInfo Jsonb `json:"pid_info"`
Pids []int `json:"pids"` // List of process IDs associated with the job
Pgids []int `json:"pgids"` // List of process group IDs associated with the job
InteractiveJobURL string `json:"interactive_job_url"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
JobRusage Jsonb `json:"job_rusage"`
// max_mem/avg_mem/used_mem/cpu_time/peak_cpu*/..._efficiency are
// JobInfo columns now (bug-1736).
NumHosts int `json:"num_hosts"` // Number of hosts used by the job
FirstHost string `json:"first_host"` // First host where the job ran
AllocSlot string `json:"alloc_slots"` // Allocated slot information
PIDInfo Jsonb `json:"pid_info"`
Pids []int `json:"pids"` // List of process IDs associated with the job
Pgids []int `json:"pgids"` // List of process group IDs associated with the job
InteractiveJobURL string `json:"interactive_job_url"`
// CoreLimit/MemLimit: JobInfo has no equivalent field (these are
// submission-time resource limits, not job-state fields), but xcecli's
// JobFieldsMap accepts them as valid -o/-l display fields (bug-1674) --
@ -662,8 +674,9 @@ type JobSubmitRequest struct {
ChkpntPeriod int `json:"chkpnt_period,omitempty"`
ChkpntDir string `json:"chkpnt_dir,omitempty"`
InitChkpntPeriod int `json:"init_chkpnt_period,omitempty"`
CWD string `json:"cwd,omitempty"`
FromHost string `json:"from_host,omitempty"` // client-asserted submitting hostname (bug-1676) -- passed through unchanged, not derived server-side
CWD string `json:"cwd,omitempty"` // absolute; the job runs here on the worker (bug-1936)
ExecHome string `json:"exec_home,omitempty" example:"/home/alice"` // absolute; HOME of the job on the worker (bug-1936)
FromHost string `json:"from_host,omitempty"` // client-asserted submitting hostname (bug-1676) -- passed through unchanged, not derived server-side
OutDir string `json:"out_dir,omitempty"`
InFile string `json:"in_file,omitempty"`
OutFile string `json:"out_file,omitempty"`
@ -703,10 +716,13 @@ type ShellLimit struct {
}
type JobSubmitResponse struct {
JobID int64 `json:"jobid"`
JobID int64 `json:"jobid"`
JobIDHuman string `json:"job_id_human"` // cluster-apiserver-timestamp-seq, for xce_test/xcecli cross-reference
QueueName string `json:"queue_name"`
Message string `json:"message"`
QueueName string `json:"queue_name"`
Message string `json:"message"`
// ArraySize is the number of elements of a submitted array; omitted
// for an ordinary job (3.2, 3.3).
ArraySize int `json:"array_size,omitempty"`
}
// Job query struct in C lib
@ -748,18 +764,18 @@ type JobControlFilter struct {
type JobControlRequest struct {
Filter JobControlFilter `json:"filter" binding:"required"`
Action string `json:"action" binding:"required"`
Signal string `json:"signal,omitempty"`
Signal string `json:"signal,omitempty"` // kill only: send this signal once instead of the graceful sequence
Comment string `json:"comment,omitempty"`
ForceKill bool `json:"force_kill,omitempty"`
Method string `json:"method,omitempty"` // "criu" as default
ForceKill bool `json:"force_kill,omitempty"` // kill: SIGKILL at once (no graceful sequence)
Method string `json:"method,omitempty"` // "criu" as default
}
type JobControlJobIDRequest struct {
Action string `json:"action" binding:"required"`
Signal string `json:"signal,omitempty"` // signal value
Signal string `json:"signal,omitempty"` // kill only: send this signal once instead of the graceful sequence
Comment string `json:"comment,omitempty"`
ForceKill bool `json:"force_kill,omitempty"`
Method string `json:"method,omitempty"` // "criu" as default
ForceKill bool `json:"force_kill,omitempty"` // kill: SIGKILL at once (no graceful sequence)
Method string `json:"method,omitempty"` // "criu" as default
}
type JobControlEvent struct {

View file

@ -1,139 +1,139 @@
package models
type ValueType int
const (
STRING ValueType = iota // Bool value
NUMERIC // Numeric value
BOOLEAN // String value
)
// String returns the string representation of ValueType
func (v ValueType) String() string {
switch v {
case BOOLEAN:
return "Boolean"
case NUMERIC:
return "Numeric"
case STRING:
return "String"
default:
return "Unknown"
}
}
type OrderType int
const (
INCR OrderType = iota
DECR
NA
)
// String returns the string representation of OrderType
func (o OrderType) String() string {
switch o {
case INCR:
return "Incr"
case DECR:
return "Decr"
case NA:
return "NA"
default:
return "Unknown"
}
}
var RESOURCE_DEFAULT_COLUMN = []string{
"res_name",
"description",
"value_type",
"order_type",
"flag",
"interval",
}
var RESOURCE_DISTRIBUTION_DEFAULT_COLUMN = []string{
"res_name",
"numeric_value",
"location",
"is_dynamic",
}
var RESOURCE_DEFINITION_DEFAULT_COLUMN = []string{
"res_name",
"description",
"value_type",
"order_type",
"flag",
"interval",
}
var ResourceFieldMappings = map[string]string{
// API field name → struct field name
"value": "numeric_value",
}
var ResourcesAvailableTypes = []string{"static", "dynamic"}
const (
BUILTIN = 0x01
DYNAMIC = 0x02
RELEASE = 0x20
CONSUMABLE = 0x80
)
type FlagValue int
// ToJSON returns the JSON object representation of FlagValue
func (f FlagValue) ToJSON() map[string]bool {
return map[string]bool{
"builtin": f&BUILTIN != 0,
"dynamic": f&DYNAMIC != 0,
"release": f&RELEASE != 0,
"consumable": f&CONSUMABLE != 0,
}
}
type ResourceInfo struct {
ResName string
Description string
ValueType ValueType
OrderType OrderType
Flag int
Interval int
}
// queues info query supported filter
var RESOURCE_DISTRIBUTION_SUPPORT_FILTERS = []string{
"res_name",
"is_dynamic",
"location",
}
var RESOURCE_DEFINITION_SUPPORT_FILTERS = []string{
"res_name",
}
type ResourceDistributionInfo struct {
ResourceName string `json:"res_name"`
IsDynamic bool `json:"is_dynamic"`
ValueType int `json:"value_type"`
BooleanValue bool `json:"boolean_value"`
StringValue string `json:"string_value"`
NumericValue float64 `json:"numeric_value"`
Location string `json:"location"`
}
type ResourceDefinitionInfo struct {
ResourceName string `json:"res_name"`
Description string `json:"description"`
ValueType int `json:"value_type"`
OrderType int `json:"order_type"`
Flag int `json:"flag"`
Interval int `json:"interval"`
}
var RESOURCE_DISTRIBUTION_SUPPORT_SORT = []string{
"res_name",
}
package models
type ValueType int
const (
STRING ValueType = iota // Bool value
NUMERIC // Numeric value
BOOLEAN // String value
)
// String returns the string representation of ValueType
func (v ValueType) String() string {
switch v {
case BOOLEAN:
return "Boolean"
case NUMERIC:
return "Numeric"
case STRING:
return "String"
default:
return "Unknown"
}
}
type OrderType int
const (
INCR OrderType = iota
DECR
NA
)
// String returns the string representation of OrderType
func (o OrderType) String() string {
switch o {
case INCR:
return "Incr"
case DECR:
return "Decr"
case NA:
return "NA"
default:
return "Unknown"
}
}
var RESOURCE_DEFAULT_COLUMN = []string{
"res_name",
"description",
"value_type",
"order_type",
"flag",
"interval",
}
var RESOURCE_DISTRIBUTION_DEFAULT_COLUMN = []string{
"res_name",
"numeric_value",
"location",
"is_dynamic",
}
var RESOURCE_DEFINITION_DEFAULT_COLUMN = []string{
"res_name",
"description",
"value_type",
"order_type",
"flag",
"interval",
}
var ResourceFieldMappings = map[string]string{
// API field name → struct field name
"value": "numeric_value",
}
var ResourcesAvailableTypes = []string{"static", "dynamic"}
const (
BUILTIN = 0x01
DYNAMIC = 0x02
RELEASE = 0x20
CONSUMABLE = 0x80
)
type FlagValue int
// ToJSON returns the JSON object representation of FlagValue
func (f FlagValue) ToJSON() map[string]bool {
return map[string]bool{
"builtin": f&BUILTIN != 0,
"dynamic": f&DYNAMIC != 0,
"release": f&RELEASE != 0,
"consumable": f&CONSUMABLE != 0,
}
}
type ResourceInfo struct {
ResName string
Description string
ValueType ValueType
OrderType OrderType
Flag int
Interval int
}
// queues info query supported filter
var RESOURCE_DISTRIBUTION_SUPPORT_FILTERS = []string{
"res_name",
"is_dynamic",
"location",
}
var RESOURCE_DEFINITION_SUPPORT_FILTERS = []string{
"res_name",
}
type ResourceDistributionInfo struct {
ResourceName string `json:"res_name"`
IsDynamic bool `json:"is_dynamic"`
ValueType int `json:"value_type"`
BooleanValue bool `json:"boolean_value"`
StringValue string `json:"string_value"`
NumericValue float64 `json:"numeric_value"`
Location string `json:"location"`
}
type ResourceDefinitionInfo struct {
ResourceName string `json:"res_name"`
Description string `json:"description"`
ValueType int `json:"value_type"`
OrderType int `json:"order_type"`
Flag int `json:"flag"`
Interval int `json:"interval"`
}
var RESOURCE_DISTRIBUTION_SUPPORT_SORT = []string{
"res_name",
}

View file

@ -0,0 +1,10 @@
package models
// UserGroupInfo is a user group from the cluster configuration
// (users.yaml usergroups). A member is a user name, another group's name or
// "all"; ExMembers are removed from the group (bug-1934).
type UserGroupInfo struct {
Name string `json:"name"`
Members []string `json:"members"`
ExMembers []string `json:"exclude_members"`
}