fix: preserve admin-set host state across worker re-registration in makeWorker_

When a worker re-joins and already exists in workermgr, makeWorker_()
now overrides the existing slot instead of returning EEXIST. Admin-set
flags (LOCKEDU, CLOSED, HIDDEN, CLOSED_BY_ADMIN) survive the override.

Add XCE_WORKER_CLOSED_BY_ADMIN flag to distinguish admin-closed hosts
from workload-closed ones. worker_ctrl sets/clears it alongside CLOSED.

Refactor worker_init/worker_destroy into copy_worker/kill_worker helpers
shared by both fresh creation and override paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-05-13 10:38:39 +00:00
parent d47b3375e7
commit febb4a6c4d
6 changed files with 111 additions and 57 deletions

View file

@ -616,13 +616,13 @@ int worker_ctrl(cluster_t* c, xce_hostctrl_t* ctrl)
if (!(w->host.status & XCE_WORKER_CLOSED)) { if (!(w->host.status & XCE_WORKER_CLOSED)) {
goto clean; goto clean;
} }
w->host.status &= ~XCE_WORKER_CLOSED; w->host.status &= ~(XCE_WORKER_CLOSED | XCE_WORKER_CLOSED_BY_ADMIN);
break; break;
case HOST_CLOSE: case HOST_CLOSE:
if (w->host.status & XCE_WORKER_CLOSED) { if (w->host.status & XCE_WORKER_CLOSED) {
goto clean; goto clean;
} }
w->host.status |= XCE_WORKER_CLOSED; w->host.status |= (XCE_WORKER_CLOSED | XCE_WORKER_CLOSED_BY_ADMIN);
break; break;
default: default:
rc = -1; rc = -1;

View file

@ -536,21 +536,16 @@ func validateHost(ctx *gin.Context, control int, hostnames []string) (successHos
} }
func isHostControlActionAllowed(actionType int, status int) bool { func isHostControlActionAllowed(actionType int, status int) bool {
isClosed := (status&models.HOSTSTAT["CLOSED"]) != 0 ||
(status&models.HOSTSTAT["CLOSED_BY_ADMIN"]) != 0
switch actionType { switch actionType {
case 1: // open strictly case 1: // open
if status&models.HOSTSTAT["CLOSED"] != 0 { return isClosed
return true
}
case 2: // close case 2: // close
if (status & models.HOSTSTAT["CLOSED"]) != 0 { return !isClosed
return false
} else {
return true
}
default: default:
return false return false
} }
return false
} }
func preCheckHostControlRequest(req *models.HostControlRequest) (bool, error) { func preCheckHostControlRequest(req *models.HostControlRequest) (bool, error) {
logger.Debug("Enter preCheckHostControlRequest") logger.Debug("Enter preCheckHostControlRequest")

View file

@ -10,16 +10,19 @@ const (
var SupportedHostActions = []string{"open", "close"} var SupportedHostActions = []string{"open", "close"}
var HOSTSTAT = map[string]int{ var HOSTSTAT = map[string]int{
"UNAVAIL": 0x00010000, "UNAVAIL": 0x00010000,
"LOCKEDU": 0x00020000, "LOCKEDU": 0x00020000,
"LOCKEDW": 0x00040000, "LOCKEDW": 0x00040000,
"BUSY": 0x00080000, "BUSY": 0x00080000,
"CLOSED": 0x00100000, "CLOSED": 0x00100000,
"CLOSED_BY_ADMIN": 0x00400000,
} }
func HostStateStr(state int) string { func HostStateStr(state int) string {
if (state & HOSTSTAT["UNAVAIL"]) != 0 { if (state & HOSTSTAT["UNAVAIL"]) != 0 {
return "UNAVAIL" return "UNAVAIL"
} else if (state & HOSTSTAT["CLOSED_BY_ADMIN"]) != 0 {
return "CLOSED_BY_ADMIN"
} else if (state & HOSTSTAT["CLOSED"]) != 0 { } else if (state & HOSTSTAT["CLOSED"]) != 0 {
return "CLOSED" return "CLOSED"
} else if (state & HOSTSTAT["BUSY"]) != 0 { } else if (state & HOSTSTAT["BUSY"]) != 0 {

View file

@ -23,6 +23,11 @@ func TestHostStateStr(t *testing.T) {
state: HOSTSTAT["CLOSED"], state: HOSTSTAT["CLOSED"],
want: "CLOSED", want: "CLOSED",
}, },
{
name: "CLOSED_BY_ADMIN flag",
state: HOSTSTAT["CLOSED_BY_ADMIN"],
want: "CLOSED_BY_ADMIN",
},
{ {
name: "BUSY flag", name: "BUSY flag",
state: HOSTSTAT["BUSY"], state: HOSTSTAT["BUSY"],
@ -48,6 +53,11 @@ func TestHostStateStr(t *testing.T) {
state: HOSTSTAT["UNAVAIL"] | HOSTSTAT["CLOSED"], state: HOSTSTAT["UNAVAIL"] | HOSTSTAT["CLOSED"],
want: "UNAVAIL", // UNAVAIL takes priority want: "UNAVAIL", // UNAVAIL takes priority
}, },
{
name: "combined CLOSED+CLOSED_BY_ADMIN flags",
state: HOSTSTAT["CLOSED"] | HOSTSTAT["CLOSED_BY_ADMIN"],
want: "CLOSED_BY_ADMIN", // CLOSED_BY_ADMIN overrides generic CLOSED
},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -172,11 +172,13 @@ typedef struct {
#define XCE_WORKER_LOCKEDW (0x00040000) #define XCE_WORKER_LOCKEDW (0x00040000)
#define XCE_WORKER_BUSY (0x00080000) #define XCE_WORKER_BUSY (0x00080000)
#define XCE_WORKER_CLOSED (0x00100000) #define XCE_WORKER_CLOSED (0x00100000)
#define XCE_WORKER_HIDDEN (0x00200000) /* Admin explicitly removed — invisible but not yet cleaned up */ #define XCE_WORKER_HIDDEN (0x00200000) /* Admin explicitly removed — invisible but not yet cleaned up */
#define XCE_WORKER_CLOSED_BY_ADMIN (0x00400000) /* Admin closed — host closed explicitly by admin */
#define IS_WORKER_UNAVAIL(status) (((status) & XCE_WORKER_UNAVAIL) != 0) #define IS_WORKER_UNAVAIL(status) (((status) & XCE_WORKER_UNAVAIL) != 0)
#define IS_WORKER_LOCKEDU(status) (((status) & XCE_WORKER_LOCKEDU) != 0) #define IS_WORKER_LOCKEDU(status) (((status) & XCE_WORKER_LOCKEDU) != 0)
#define IS_WORKER_LOCKEDW(status) (((status) & XCE_WORKER_LOCKEDW) != 0) #define IS_WORKER_LOCKEDW(status) (((status) & XCE_WORKER_LOCKEDW) != 0)
#define IS_WORKER_CLOSED_BY_ADMIN(status) (((status) & XCE_WORKER_CLOSED_BY_ADMIN) != 0)
#define IS_WORKER_LOCKED(status) \ #define IS_WORKER_LOCKED(status) \
(IS_WORKER_LOCKEDU(status) || IS_WORKER_LOCKEDW(status)) (IS_WORKER_LOCKEDU(status) || IS_WORKER_LOCKEDW(status))
#define IS_WORKER_BUSY(status) (((status) & XCE_WORKER_BUSY) != 0) #define IS_WORKER_BUSY(status) (((status) & XCE_WORKER_BUSY) != 0)

View file

@ -22,12 +22,34 @@ static size_t worker_datasize(va_list ap)
return sz; return sz;
} }
static int worker_init(meta_obj_t* obj, va_list ap) static void kill_worker(worker_t* w)
{ {
worker_t* dst = RTTI_OBJ_TO_DATA(worker_t, obj); dereference(w->host.name);
worker_private_t* priv = (worker_private_t*)RTTI_OBJ_TO_PRIV(obj);
xce_worker_t* src = va_arg(ap, xce_worker_t*);
if (w->host.attrs != NULL) {
dereference(w->host.attrs->hosttype);
dereference(w->host.attrs->hostmodel);
FREEUP(w->host.attrs);
}
if (w->host.dispatch_window_config != NULL) {
free_timewindow_config(w->host.dispatch_window_config);
w->host.dispatch_window_config = NULL;
}
if (w->host.thresholds != NULL) {
free_resource_thresholds(w->host.thresholds, w->host.nthresholds);
w->host.thresholds = NULL;
w->host.nthresholds = 0;
}
zarray_fini(&(w->host.resvals));
return;
}
static int copy_worker(worker_t* dst, xce_worker_t* src)
{
if ((src == NULL) || (src->attrs == NULL)) { if ((src == NULL) || (src->attrs == NULL)) {
return -EINVAL; return -EINVAL;
} }
@ -55,6 +77,42 @@ static int worker_init(meta_obj_t* obj, va_list ap)
dst->host.nresvals_to_sched = 0; dst->host.nresvals_to_sched = 0;
dst->host.resvals_to_sched = NULL; dst->host.resvals_to_sched = NULL;
return 1;
}
static int override_worker(worker_t* dst, xce_worker_t* w)
{
#define XCE_WORKER_ADMIN_MASK \
(XCE_WORKER_LOCKEDU | XCE_WORKER_CLOSED | XCE_WORKER_HIDDEN | XCE_WORKER_CLOSED_BY_ADMIN)
uint32_t admin_status = 0;
int rc = 0;
admin_status = dst->host.status & XCE_WORKER_ADMIN_MASK;
kill_worker(dst);
rc = copy_worker(dst, w);
if (rc >= 0) {
dst->host.status |= admin_status;
dst->ep = NULL;
}
return rc;
#undef XCE_WORKER_ADMIN_MASK
}
static int worker_init(meta_obj_t* obj, va_list ap)
{
worker_t* dst = RTTI_OBJ_TO_DATA(worker_t, obj);
worker_private_t* priv = (worker_private_t*)RTTI_OBJ_TO_PRIV(obj);
xce_worker_t* src = va_arg(ap, xce_worker_t*);
int rc = 0;
rc = copy_worker(dst, src);
if (rc < 0) {
return rc;
}
my_rwlock_init(&(priv->lock)); my_rwlock_init(&(priv->lock));
priv->refcount = 1; priv->refcount = 1;
@ -66,26 +124,7 @@ static void worker_destroy(meta_obj_t* obj)
worker_t* w = RTTI_OBJ_TO_DATA(worker_t, obj); worker_t* w = RTTI_OBJ_TO_DATA(worker_t, obj);
worker_private_t* priv = (worker_private_t*)RTTI_OBJ_TO_PRIV(obj); worker_private_t* priv = (worker_private_t*)RTTI_OBJ_TO_PRIV(obj);
dereference(w->host.name); kill_worker(w);
if (w->host.attrs != NULL) {
dereference(w->host.attrs->hosttype);
dereference(w->host.attrs->hostmodel);
FREEUP(w->host.attrs);
}
if (w->host.dispatch_window_config != NULL) {
free_timewindow_config(w->host.dispatch_window_config);
w->host.dispatch_window_config = NULL;
}
if (w->host.thresholds != NULL) {
free_resource_thresholds(w->host.thresholds, w->host.nthresholds);
w->host.thresholds = NULL;
w->host.nthresholds = 0;
}
zarray_fini(&(w->host.resvals));
my_rwlock_fini(&(priv->lock)); my_rwlock_fini(&(priv->lock));
memset(priv, 0x0, sizeof(*priv)); memset(priv, 0x0, sizeof(*priv));
@ -163,9 +202,10 @@ static typeinfo_t worker_type = {
static worker_t* makeWorker_(var workermgr, xce_worker_t* w) static worker_t* makeWorker_(var workermgr, xce_worker_t* w)
{ {
worker_t* worker = NULL; worker_t* worker = NULL;
worker_t* w1 = NULL;
int rc = 0; int rc = 0;
rc = key_find(workermgr, (void*)w->name, (void**)&worker); rc = key_find(workermgr, (void*)w->name, (void**)&w1);
if ((rc < 0) && (rc != -ENOENT)) { if ((rc < 0) && (rc != -ENOENT)) {
errno = -rc; errno = -rc;
@ -174,20 +214,24 @@ static worker_t* makeWorker_(var workermgr, xce_worker_t* w)
} }
if (rc > 0) { if (rc > 0) {
errno = EEXIST; rc = override_worker(w1, w);
return NULL;
}
worker = makeInstance(&worker_type, w);
if (worker != NULL) {
rc = key_add(workermgr, (void*)worker->host.name, (void*)worker);
if (rc < 0) { if (rc < 0) {
destroy(worker);
errno = -rc;
return NULL; return NULL;
} }
worker = w1;
} else {
worker = makeInstance(&worker_type, w);
if (worker != NULL) {
rc = key_add(workermgr, (void*)worker->host.name, (void*)worker);
if (rc < 0) {
destroy(worker);
errno = -rc;
return NULL;
}
}
} }
return worker; return worker;