DRA: move dra/checkpont/* to dra/state/*
This commit is contained in:
@@ -25,7 +25,6 @@ import (
|
|||||||
resourceapi "k8s.io/api/resource/v1alpha3"
|
resourceapi "k8s.io/api/resource/v1alpha3"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/cm/dra/checkpoint"
|
|
||||||
state "k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
|
state "k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
|
||||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
)
|
)
|
||||||
@@ -41,7 +40,7 @@ type ClaimInfo struct {
|
|||||||
// claimInfoCache is a cache of processed resource claims keyed by namespace/claimname.
|
// claimInfoCache is a cache of processed resource claims keyed by namespace/claimname.
|
||||||
type claimInfoCache struct {
|
type claimInfoCache struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
state checkpoint.Checkpointer
|
state state.Checkpointer
|
||||||
claimInfo map[string]*ClaimInfo
|
claimInfo map[string]*ClaimInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +113,7 @@ func (info *ClaimInfo) isPrepared() bool {
|
|||||||
|
|
||||||
// newClaimInfoCache creates a new claim info cache object, pre-populated from a checkpoint (if present).
|
// newClaimInfoCache creates a new claim info cache object, pre-populated from a checkpoint (if present).
|
||||||
func newClaimInfoCache(stateDir, checkpointName string) (*claimInfoCache, error) {
|
func newClaimInfoCache(stateDir, checkpointName string) (*claimInfoCache, error) {
|
||||||
checkpointer, err := checkpoint.NewCheckpointer(stateDir, checkpointName)
|
checkpointer, err := state.NewCheckpointer(stateDir, checkpointName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not initialize checkpoint manager, please drain node and remove dra state file, err: %+v", err)
|
return nil, fmt.Errorf("could not initialize checkpoint manager, please drain node and remove dra state file, err: %+v", err)
|
||||||
}
|
}
|
||||||
@@ -198,7 +197,7 @@ func (cache *claimInfoCache) syncToCheckpoint() error {
|
|||||||
for _, infoClaim := range cache.claimInfo {
|
for _, infoClaim := range cache.claimInfo {
|
||||||
claimInfoStateList = append(claimInfoStateList, infoClaim.ClaimInfoState)
|
claimInfoStateList = append(claimInfoStateList, infoClaim.ClaimInfoState)
|
||||||
}
|
}
|
||||||
checkpoint, err := checkpoint.NewCheckpoint(claimInfoStateList)
|
checkpoint, err := state.NewCheckpoint(claimInfoStateList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package checkpoint
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -22,19 +22,17 @@ import (
|
|||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors"
|
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors"
|
||||||
state "k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CheckpointAPIGroup = "checkpoint.dra.kubelet.k8s.io"
|
CheckpointAPIGroup = "checkpoint.dra.kubelet.k8s.io"
|
||||||
CheckpointKind = "DRACheckpoint"
|
CheckpointKind = "DRACheckpoint"
|
||||||
CheckpointAPIVersion = CheckpointAPIGroup + "/" + state.Version
|
CheckpointAPIVersion = CheckpointAPIGroup + "/" + Version
|
||||||
)
|
)
|
||||||
|
|
||||||
// Checkpoint represents a structure to store DRA checkpoint data
|
// Checkpoint represents a structure to store DRA checkpoint data
|
||||||
type Checkpoint struct {
|
type Checkpoint struct {
|
||||||
// Data is a JSON serialized checkpoint data
|
// Data is a JSON serialized checkpoint data
|
||||||
// See state.CheckpointData for the details
|
|
||||||
Data string
|
Data string
|
||||||
// Checksum is a checksum of Data
|
// Checksum is a checksum of Data
|
||||||
Checksum uint32
|
Checksum uint32
|
||||||
@@ -42,11 +40,11 @@ type Checkpoint struct {
|
|||||||
|
|
||||||
type CheckpointData struct {
|
type CheckpointData struct {
|
||||||
metav1.TypeMeta
|
metav1.TypeMeta
|
||||||
Entries state.ClaimInfoStateList
|
Entries ClaimInfoStateList
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCheckpoint creates a new checkpoint from a list of claim info states
|
// NewCheckpoint creates a new checkpoint from a list of claim info states
|
||||||
func NewCheckpoint(data state.ClaimInfoStateList) (*Checkpoint, error) {
|
func NewCheckpoint(data ClaimInfoStateList) (*Checkpoint, error) {
|
||||||
cpData := &CheckpointData{
|
cpData := &CheckpointData{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: CheckpointKind,
|
Kind: CheckpointKind,
|
||||||
@@ -97,7 +95,7 @@ func (cp *Checkpoint) VerifyChecksum() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEntries returns list of claim info states from checkpoint
|
// GetEntries returns list of claim info states from checkpoint
|
||||||
func (cp *Checkpoint) GetEntries() (state.ClaimInfoStateList, error) {
|
func (cp *Checkpoint) GetEntries() (ClaimInfoStateList, error) {
|
||||||
var cpData CheckpointData
|
var cpData CheckpointData
|
||||||
if err := json.Unmarshal([]byte(cp.Data), &cpData); err != nil {
|
if err := json.Unmarshal([]byte(cp.Data), &cpData); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package checkpoint
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager"
|
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager"
|
||||||
checkpointerrors "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors"
|
checkpointerrors "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/errors"
|
||||||
state "k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Checkpointer interface {
|
type Checkpointer interface {
|
||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package checkpoint
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager"
|
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager"
|
||||||
testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing"
|
testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing"
|
||||||
state "k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const testingCheckpoint = "dramanager_checkpoint_test"
|
const testingCheckpoint = "dramanager_checkpoint_test"
|
||||||
@@ -39,7 +38,7 @@ func TestCheckpointGetOrCreate(t *testing.T) {
|
|||||||
description string
|
description string
|
||||||
checkpointContent string
|
checkpointContent string
|
||||||
expectedError string
|
expectedError string
|
||||||
expectedClaimInfoStateList state.ClaimInfoStateList
|
expectedClaimInfoStateList ClaimInfoStateList
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "new-checkpoint",
|
description: "new-checkpoint",
|
||||||
@@ -48,11 +47,11 @@ func TestCheckpointGetOrCreate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "single-claim-info-state",
|
description: "single-claim-info-state",
|
||||||
checkpointContent: `{"Data":"{\"kind\":\"DRACheckpoint\",\"apiVersion\":\"checkpoint.dra.kubelet.k8s.io/v1\",\"Entries\":[{\"ClaimUID\":\"067798be-454e-4be4-9047-1aa06aea63f7\",\"ClaimName\":\"example\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-1\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]}]}}}]}","Checksum":1597924435}`,
|
checkpointContent: `{"Data":"{\"kind\":\"DRACheckpoint\",\"apiVersion\":\"checkpoint.dra.kubelet.k8s.io/v1\",\"Entries\":[{\"ClaimUID\":\"067798be-454e-4be4-9047-1aa06aea63f7\",\"ClaimName\":\"example\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-1\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]}]}}}]}","Checksum":1597924435}`,
|
||||||
expectedClaimInfoStateList: state.ClaimInfoStateList{
|
expectedClaimInfoStateList: ClaimInfoStateList{
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-1",
|
DeviceName: "dev-1",
|
||||||
@@ -72,11 +71,11 @@ func TestCheckpointGetOrCreate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "claim-info-state-with-multiple-devices",
|
description: "claim-info-state-with-multiple-devices",
|
||||||
checkpointContent: `{"Data":"{\"kind\":\"DRACheckpoint\",\"apiVersion\":\"checkpoint.dra.kubelet.k8s.io/v1\",\"Entries\":[{\"ClaimUID\":\"067798be-454e-4be4-9047-1aa06aea63f7\",\"ClaimName\":\"example\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-1\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]},{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-2\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]}]}}}]}","Checksum":1812303514}`,
|
checkpointContent: `{"Data":"{\"kind\":\"DRACheckpoint\",\"apiVersion\":\"checkpoint.dra.kubelet.k8s.io/v1\",\"Entries\":[{\"ClaimUID\":\"067798be-454e-4be4-9047-1aa06aea63f7\",\"ClaimName\":\"example\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-1\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]},{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-2\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]}]}}}]}","Checksum":1812303514}`,
|
||||||
expectedClaimInfoStateList: state.ClaimInfoStateList{
|
expectedClaimInfoStateList: ClaimInfoStateList{
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-1",
|
DeviceName: "dev-1",
|
||||||
@@ -102,11 +101,11 @@ func TestCheckpointGetOrCreate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "two-claim-info-states",
|
description: "two-claim-info-states",
|
||||||
checkpointContent: `{"Data":"{\"kind\":\"DRACheckpoint\",\"apiVersion\":\"checkpoint.dra.kubelet.k8s.io/v1\",\"Entries\":[{\"ClaimUID\":\"067798be-454e-4be4-9047-1aa06aea63f7\",\"ClaimName\":\"example-1\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-1\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]}]}}},{\"ClaimUID\":\"4cf8db2d-06c0-7d70-1a51-e59b25b2c16c\",\"ClaimName\":\"example-2\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-2\",\"RequestNames\":null,\"CDIDeviceIDs\":null}]}}}]}","Checksum":3633532417}`,
|
checkpointContent: `{"Data":"{\"kind\":\"DRACheckpoint\",\"apiVersion\":\"checkpoint.dra.kubelet.k8s.io/v1\",\"Entries\":[{\"ClaimUID\":\"067798be-454e-4be4-9047-1aa06aea63f7\",\"ClaimName\":\"example-1\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-1\",\"RequestNames\":[\"test request\"],\"CDIDeviceIDs\":[\"example.com/example=cdi-example\"]}]}}},{\"ClaimUID\":\"4cf8db2d-06c0-7d70-1a51-e59b25b2c16c\",\"ClaimName\":\"example-2\",\"Namespace\":\"default\",\"PodUIDs\":{\"139cdb46-f989-4f17-9561-ca10cfb509a6\":{}},\"DriverState\":{\"test-driver.cdi.k8s.io\":{\"Devices\":[{\"PoolName\":\"worker-1\",\"DeviceName\":\"dev-2\",\"RequestNames\":null,\"CDIDeviceIDs\":null}]}}}]}","Checksum":3633532417}`,
|
||||||
expectedClaimInfoStateList: state.ClaimInfoStateList{
|
expectedClaimInfoStateList: ClaimInfoStateList{
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-1",
|
DeviceName: "dev-1",
|
||||||
@@ -122,9 +121,9 @@ func TestCheckpointGetOrCreate(t *testing.T) {
|
|||||||
PodUIDs: sets.New("139cdb46-f989-4f17-9561-ca10cfb509a6"),
|
PodUIDs: sets.New("139cdb46-f989-4f17-9561-ca10cfb509a6"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-2",
|
DeviceName: "dev-2",
|
||||||
@@ -197,16 +196,16 @@ func TestCheckpointGetOrCreate(t *testing.T) {
|
|||||||
func TestCheckpointStateStore(t *testing.T) {
|
func TestCheckpointStateStore(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
claimInfoStateList state.ClaimInfoStateList
|
claimInfoStateList ClaimInfoStateList
|
||||||
expectedCheckpointContent string
|
expectedCheckpointContent string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "single-claim-info-state",
|
description: "single-claim-info-state",
|
||||||
claimInfoStateList: state.ClaimInfoStateList{
|
claimInfoStateList: ClaimInfoStateList{
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-1",
|
DeviceName: "dev-1",
|
||||||
@@ -226,11 +225,11 @@ func TestCheckpointStateStore(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "single-claim-info-state-with-multiple-devices",
|
description: "single-claim-info-state-with-multiple-devices",
|
||||||
claimInfoStateList: state.ClaimInfoStateList{
|
claimInfoStateList: ClaimInfoStateList{
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-1",
|
DeviceName: "dev-1",
|
||||||
@@ -256,11 +255,11 @@ func TestCheckpointStateStore(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "two-claim-info-states",
|
description: "two-claim-info-states",
|
||||||
claimInfoStateList: state.ClaimInfoStateList{
|
claimInfoStateList: ClaimInfoStateList{
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-1",
|
DeviceName: "dev-1",
|
||||||
@@ -276,9 +275,9 @@ func TestCheckpointStateStore(t *testing.T) {
|
|||||||
PodUIDs: sets.New("139cdb46-f989-4f17-9561-ca10cfb509a6"),
|
PodUIDs: sets.New("139cdb46-f989-4f17-9561-ca10cfb509a6"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DriverState: map[string]state.DriverState{
|
DriverState: map[string]DriverState{
|
||||||
"test-driver.cdi.k8s.io": {
|
"test-driver.cdi.k8s.io": {
|
||||||
Devices: []state.Device{
|
Devices: []Device{
|
||||||
{
|
{
|
||||||
PoolName: "worker-1",
|
PoolName: "worker-1",
|
||||||
DeviceName: "dev-2",
|
DeviceName: "dev-2",
|
||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
)
|
)
|
||||||
@@ -28,11 +27,6 @@ const (
|
|||||||
|
|
||||||
type ClaimInfoStateList []ClaimInfoState
|
type ClaimInfoStateList []ClaimInfoState
|
||||||
|
|
||||||
type CheckpointData struct {
|
|
||||||
metav1.TypeMeta
|
|
||||||
Entries ClaimInfoStateList
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
type ClaimInfoState struct {
|
type ClaimInfoState struct {
|
||||||
// ClaimUID is the UID of a resource claim
|
// ClaimUID is the UID of a resource claim
|
||||||
Reference in New Issue
Block a user