forked from rook/rook
ceph: remove DaemonType type
This type was a string already and was just making us doing string() calls all the time to it's not worth it. Signed-off-by: Sébastien Han <seb@redhat.com>
This commit is contained in:
@@ -75,7 +75,7 @@ func (r *ReconcileNode) createOrUpdateCephCrash(node corev1.Node, tolerations []
|
||||
k8sutil.AppAttr: AppName,
|
||||
NodeNameLabel: node.GetName(),
|
||||
}
|
||||
deploymentLabels[string(config.CrashType)] = "crash"
|
||||
deploymentLabels[config.CrashType] = "crash"
|
||||
deploymentLabels["ceph_daemon_id"] = "crash"
|
||||
deploymentLabels[k8sutil.ClusterAttr] = cephCluster.GetNamespace()
|
||||
|
||||
|
||||
@@ -182,8 +182,7 @@ func (c *Cluster) Start() error {
|
||||
}
|
||||
logger.Infof("deployment for mgr %s already exists. updating if needed", resourceName)
|
||||
|
||||
daemon := string(config.MgrType)
|
||||
if err := updateDeploymentAndWait(c.context, d, c.Namespace, daemon, mgrConfig.DaemonID, c.skipUpgradeChecks, false); err != nil {
|
||||
if err := updateDeploymentAndWait(c.context, d, c.Namespace, config.MgrType, mgrConfig.DaemonID, c.skipUpgradeChecks, false); err != nil {
|
||||
logger.Errorf("failed to update mgr deployment %q. %+v", resourceName, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,8 +802,7 @@ func (c *Cluster) updateMon(m *monConfig, d *apps.Deployment) error {
|
||||
logger.Infof("deployment for mon %s already exists. updating if needed",
|
||||
d.Name)
|
||||
|
||||
daemonType := string(config.MonType)
|
||||
err := updateDeploymentAndWait(c.context, d, c.Namespace, daemonType, m.DaemonName, c.spec.SkipUpgradeChecks, false)
|
||||
err := updateDeploymentAndWait(c.context, d, c.Namespace, config.MonType, m.DaemonName, c.spec.SkipUpgradeChecks, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update mon deployment %s", m.ResourceName)
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ func (c *Cluster) makeMonDaemonContainer(monConfig *monConfig) v1.Container {
|
||||
k8sutil.PodIPEnvVar(podIPEnvVar),
|
||||
),
|
||||
Resources: cephv1.GetMonResources(c.spec.Resources),
|
||||
LivenessProbe: controller.GenerateLivenessProbeExecDaemon(string(config.MonType), monConfig.DaemonName),
|
||||
LivenessProbe: controller.GenerateLivenessProbeExecDaemon(config.MonType, monConfig.DaemonName),
|
||||
}
|
||||
|
||||
// If host networking is enabled, we don't need a bind addr that is different from the public addr
|
||||
|
||||
@@ -492,9 +492,7 @@ func (c *Cluster) startOSDDaemonsOnPVC(pvcName string, config *provisionConfig,
|
||||
}
|
||||
|
||||
if createErr != nil && kerrors.IsAlreadyExists(createErr) {
|
||||
daemon := string(opconfig.OsdType)
|
||||
|
||||
if err = updateDeploymentAndWait(c.context, dp, c.Namespace, daemon, strconv.Itoa(osd.ID), c.skipUpgradeChecks, c.continueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
if err = updateDeploymentAndWait(c.context, dp, c.Namespace, opconfig.OsdType, strconv.Itoa(osd.ID), c.skipUpgradeChecks, c.continueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
logger.Errorf("failed to update osd deployment %d. %+v", osd.ID, err)
|
||||
}
|
||||
}
|
||||
@@ -574,8 +572,7 @@ func (c *Cluster) startOSDDaemonsOnNode(nodeName string, config *provisionConfig
|
||||
}
|
||||
|
||||
if createErr != nil && kerrors.IsAlreadyExists(createErr) {
|
||||
daemon := string(opconfig.OsdType)
|
||||
if err = updateDeploymentAndWait(c.context, dp, c.Namespace, daemon, strconv.Itoa(osd.ID), c.skipUpgradeChecks, c.continueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
if err = updateDeploymentAndWait(c.context, dp, c.Namespace, opconfig.OsdType, strconv.Itoa(osd.ID), c.skipUpgradeChecks, c.continueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
logger.Errorf("failed to update osd deployment %d. %+v", osd.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ func (c *Cluster) makeDeployment(osdProps osdProperties, osd OSDInfo, provisionC
|
||||
Env: envVars,
|
||||
Resources: osdProps.resources,
|
||||
SecurityContext: securityContext,
|
||||
LivenessProbe: controller.GenerateLivenessProbeExecDaemon(string(opconfig.OsdType), osdID),
|
||||
LivenessProbe: controller.GenerateLivenessProbeExecDaemon(opconfig.OsdType, osdID),
|
||||
},
|
||||
},
|
||||
Volumes: volumes,
|
||||
|
||||
@@ -138,9 +138,7 @@ func (m *Mirroring) Start() error {
|
||||
}
|
||||
logger.Infof("deployment for rbd-mirror %s already exists. updating if needed", resourceName)
|
||||
|
||||
daemon := string(config.RbdMirrorType)
|
||||
|
||||
if err := updateDeploymentAndWait(m.context, d, m.Namespace, daemon, daemonConf.DaemonID, m.skipUpgradeChecks, false); err != nil {
|
||||
if err := updateDeploymentAndWait(m.context, d, m.Namespace, config.RbdMirrorType, daemonConf.DaemonID, m.skipUpgradeChecks, false); err != nil {
|
||||
// fail could be an issue updating label selector (immutable), so try del and recreate
|
||||
logger.Debugf("updateDeploymentAndWait failed for rbd-mirror %q. Attempting del-and-recreate. %v", resourceName, err)
|
||||
err = m.context.Clientset.AppsV1().Deployments(m.Namespace).Delete(d.Name, &metav1.DeleteOptions{})
|
||||
|
||||
@@ -30,7 +30,7 @@ func (m *Mirroring) makeDeployment(daemonConfig *daemonConfig) *apps.Deployment
|
||||
podSpec := v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: daemonConfig.ResourceName,
|
||||
Labels: controller.PodLabels(AppName, m.Namespace, string(config.RbdMirrorType), daemonConfig.DaemonID),
|
||||
Labels: controller.PodLabels(AppName, m.Namespace, config.RbdMirrorType, daemonConfig.DaemonID),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
InitContainers: []v1.Container{
|
||||
@@ -59,7 +59,7 @@ func (m *Mirroring) makeDeployment(daemonConfig *daemonConfig) *apps.Deployment
|
||||
Name: daemonConfig.ResourceName,
|
||||
Namespace: m.Namespace,
|
||||
Annotations: m.annotations,
|
||||
Labels: controller.PodLabels(AppName, m.Namespace, string(config.RbdMirrorType), daemonConfig.DaemonID),
|
||||
Labels: controller.PodLabels(AppName, m.Namespace, config.RbdMirrorType, daemonConfig.DaemonID),
|
||||
},
|
||||
Spec: apps.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
@@ -105,7 +105,7 @@ func (m *Mirroring) makeMirroringDaemonContainer(daemonConfig *daemonConfig) v1.
|
||||
// Not implemented at this point since the socket name is '/run/ceph/ceph-client.rbd-mirror.a.1.94362516231272.asok'
|
||||
// Also the command to run will be:
|
||||
// ceph --admin-daemon /run/ceph/ceph-client.rbd-mirror.a.1.94362516231272.asok rbd mirror status
|
||||
// LivenessProbe: controller.GenerateLivenessProbeExecDaemon(string(config.RbdMirrorType), daemonConfig.DaemonID),
|
||||
// LivenessProbe: controller.GenerateLivenessProbeExecDaemon(config.RbdMirrorType, daemonConfig.DaemonID),
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
@@ -32,30 +32,27 @@ import (
|
||||
|
||||
var logger = capnslog.NewPackageLogger("github.com/rook/rook", "op-config")
|
||||
|
||||
// DaemonType defines the type of a daemon. e.g., mon, mgr, osd, mds, rgw
|
||||
type DaemonType string
|
||||
|
||||
const (
|
||||
// MonType defines the mon DaemonType
|
||||
MonType DaemonType = "mon"
|
||||
MonType = "mon"
|
||||
|
||||
// MgrType defines the mgr DaemonType
|
||||
MgrType DaemonType = "mgr"
|
||||
MgrType = "mgr"
|
||||
|
||||
// OsdType defines the osd DaemonType
|
||||
OsdType DaemonType = "osd"
|
||||
OsdType = "osd"
|
||||
|
||||
// MdsType defines the mds DaemonType
|
||||
MdsType DaemonType = "mds"
|
||||
MdsType = "mds"
|
||||
|
||||
// RgwType defines the rgw DaemonType
|
||||
RgwType DaemonType = "rgw"
|
||||
RgwType = "rgw"
|
||||
|
||||
// RbdMirrorType defines the rbd-mirror DaemonType
|
||||
RbdMirrorType DaemonType = "rbd-mirror"
|
||||
RbdMirrorType = "rbd-mirror"
|
||||
|
||||
// CrashType defines the crash collector DaemonType
|
||||
CrashType DaemonType = "crashcollector"
|
||||
CrashType = "crashcollector"
|
||||
|
||||
// CephUser is the Linux Ceph username
|
||||
CephUser = "ceph"
|
||||
|
||||
@@ -52,7 +52,7 @@ type DataPathMap struct {
|
||||
// which may include data from other daemons.
|
||||
func NewStatefulDaemonDataPathMap(
|
||||
dataDirHostPath, daemonDataDirHostRelativePath string,
|
||||
daemonType DaemonType, daemonID, namespace string,
|
||||
daemonType, daemonID, namespace string,
|
||||
) *DataPathMap {
|
||||
return &DataPathMap{
|
||||
HostDataDir: path.Join(dataDirHostPath, daemonDataDirHostRelativePath),
|
||||
@@ -64,7 +64,7 @@ func NewStatefulDaemonDataPathMap(
|
||||
// NewStatelessDaemonDataPathMap returns a new DataPathMap for a daemon which does not persist data
|
||||
// to the host (mgrs, mdses, rgws)
|
||||
func NewStatelessDaemonDataPathMap(
|
||||
daemonType DaemonType, daemonID, namespace, dataDirHostPath string,
|
||||
daemonType, daemonID, namespace, dataDirHostPath string,
|
||||
) *DataPathMap {
|
||||
return &DataPathMap{
|
||||
HostDataDir: "",
|
||||
@@ -83,9 +83,9 @@ func NewDatalessDaemonDataPathMap(namespace, dataDirHostPath string) *DataPathMa
|
||||
}
|
||||
}
|
||||
|
||||
func cephDataDir(daemonType DaemonType, daemonID string) string {
|
||||
func cephDataDir(daemonType, daemonID string) string {
|
||||
// daemons' default data dirs are: /var/lib/ceph/<daemon-type>/ceph-<daemon-id>
|
||||
return path.Join(VarLibCephDir, string(daemonType), "ceph-"+daemonID)
|
||||
return path.Join(VarLibCephDir, daemonType, "ceph-"+daemonID)
|
||||
}
|
||||
|
||||
// ContainerCrashDir returns the directory of the crash collector
|
||||
|
||||
@@ -506,7 +506,7 @@ func (c *daemonConfig) buildSocketPath() string {
|
||||
|
||||
func (c *daemonConfig) buildAdminSocketCommand() string {
|
||||
command := "status"
|
||||
if c.daemonType == string(config.MonType) {
|
||||
if c.daemonType == config.MonType {
|
||||
command = "mon_status"
|
||||
}
|
||||
|
||||
|
||||
@@ -104,44 +104,40 @@ func TestCheckPodMemory(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildAdminSocketCommand(t *testing.T) {
|
||||
daemonType := string(config.OsdType)
|
||||
c := getDaemonConfig(daemonType, "")
|
||||
c := getDaemonConfig(config.OsdType, "")
|
||||
|
||||
command := c.buildAdminSocketCommand()
|
||||
assert.Equal(t, "status", command)
|
||||
|
||||
c.daemonType = string(config.MonType)
|
||||
c.daemonType = config.MonType
|
||||
command = c.buildAdminSocketCommand()
|
||||
assert.Equal(t, "mon_status", command)
|
||||
}
|
||||
|
||||
func TestBuildSocketName(t *testing.T) {
|
||||
daemonType := string(config.OsdType)
|
||||
daemonID := "0"
|
||||
c := getDaemonConfig(daemonType, daemonID)
|
||||
c := getDaemonConfig(config.OsdType, daemonID)
|
||||
|
||||
socketName := c.buildSocketName()
|
||||
assert.Equal(t, "ceph-osd.0.asok", socketName)
|
||||
|
||||
c.daemonType = string(config.MonType)
|
||||
c.daemonType = config.MonType
|
||||
c.daemonID = "a"
|
||||
socketName = c.buildSocketName()
|
||||
assert.Equal(t, "ceph-mon.a.asok", socketName)
|
||||
}
|
||||
|
||||
func TestBuildSocketPath(t *testing.T) {
|
||||
daemonType := string(config.OsdType)
|
||||
daemonID := "0"
|
||||
c := getDaemonConfig(daemonType, daemonID)
|
||||
c := getDaemonConfig(config.OsdType, daemonID)
|
||||
|
||||
socketPath := c.buildSocketPath()
|
||||
assert.Equal(t, "/run/ceph/ceph-osd.0.asok", socketPath)
|
||||
}
|
||||
|
||||
func TestGenerateLivenessProbeExecDaemon(t *testing.T) {
|
||||
daemonType := string(config.OsdType)
|
||||
daemonID := "0"
|
||||
probe := GenerateLivenessProbeExecDaemon(daemonType, daemonID)
|
||||
probe := GenerateLivenessProbeExecDaemon(config.OsdType, daemonID)
|
||||
expectedCommand := []string{"env",
|
||||
"-i",
|
||||
"sh",
|
||||
|
||||
@@ -190,10 +190,9 @@ func (r *ReconcileCephFilesystem) reconcile(request reconcile.Request) (reconcil
|
||||
r.clusterInfo = clusterInfo
|
||||
|
||||
// Populate CephVersion
|
||||
daemon := string(opconfig.MonType)
|
||||
currentCephVersion, err := cephclient.LeastUptodateDaemonVersion(r.context, r.clusterInfo.Name, daemon)
|
||||
currentCephVersion, err := cephclient.LeastUptodateDaemonVersion(r.context, r.clusterInfo.Name, opconfig.MonType)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to retrieve current ceph %q version", daemon)
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to retrieve current ceph %q version", opconfig.MonType)
|
||||
}
|
||||
r.clusterInfo.CephVersion = currentCephVersion
|
||||
|
||||
|
||||
@@ -186,8 +186,7 @@ func (c *Cluster) Start() error {
|
||||
}
|
||||
|
||||
if createErr != nil && kerrors.IsAlreadyExists(createErr) {
|
||||
daemon := string(config.MdsType)
|
||||
if err = UpdateDeploymentAndWait(c.context, d, c.fs.Namespace, daemon, daemonLetterID, c.clusterSpec.SkipUpgradeChecks, c.clusterSpec.ContinueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
if err = UpdateDeploymentAndWait(c.context, d, c.fs.Namespace, config.MdsType, daemonLetterID, c.clusterSpec.SkipUpgradeChecks, c.clusterSpec.ContinueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
return errors.Wrapf(err, "failed to update mds deployment %s", d.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (c *Cluster) makeMdsDaemonContainer(mdsConfig *mdsConfig) v1.Container {
|
||||
),
|
||||
Resources: c.fs.Spec.MetadataServer.Resources,
|
||||
SecurityContext: mon.PodSecurityContext(),
|
||||
LivenessProbe: controller.GenerateLivenessProbeExecDaemon(string(config.MdsType), mdsConfig.DaemonID),
|
||||
LivenessProbe: controller.GenerateLivenessProbeExecDaemon(config.MdsType, mdsConfig.DaemonID),
|
||||
}
|
||||
|
||||
return container
|
||||
|
||||
@@ -189,10 +189,9 @@ func (r *ReconcileCephNFS) reconcile(request reconcile.Request) (reconcile.Resul
|
||||
r.clusterInfo = clusterInfo
|
||||
|
||||
// Populate CephVersion
|
||||
daemon := string(opconfig.MonType)
|
||||
currentCephVersion, err := cephclient.LeastUptodateDaemonVersion(r.context, r.clusterInfo.Name, daemon)
|
||||
currentCephVersion, err := cephclient.LeastUptodateDaemonVersion(r.context, r.clusterInfo.Name, opconfig.MonType)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to retrieve current ceph %q version", daemon)
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to retrieve current ceph %q version", opconfig.MonType)
|
||||
}
|
||||
r.clusterInfo.CephVersion = currentCephVersion
|
||||
|
||||
|
||||
@@ -191,10 +191,9 @@ func (r *ReconcileCephObjectStore) reconcile(request reconcile.Request) (reconci
|
||||
r.clusterInfo = clusterInfo
|
||||
|
||||
// Populate CephVersion
|
||||
daemon := string(opconfig.MonType)
|
||||
currentCephVersion, err := cephclient.LeastUptodateDaemonVersion(r.context, r.clusterInfo.Name, daemon)
|
||||
currentCephVersion, err := cephclient.LeastUptodateDaemonVersion(r.context, r.clusterInfo.Name, opconfig.MonType)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to retrieve current ceph %q version", daemon)
|
||||
return reconcile.Result{}, errors.Wrapf(err, "failed to retrieve current ceph %q version", opconfig.MonType)
|
||||
}
|
||||
r.clusterInfo.CephVersion = currentCephVersion
|
||||
|
||||
|
||||
@@ -164,8 +164,7 @@ func (c *clusterConfig) startRGWPods() error {
|
||||
|
||||
// Generate the mime.types file after the rep. controller as well for the same reason as keyring
|
||||
if createErr != nil && kerrors.IsAlreadyExists(createErr) {
|
||||
daemon := string(config.RgwType)
|
||||
if err := updateDeploymentAndWait(c.context, deployment, c.store.Namespace, daemon, daemonLetterID, c.skipUpgradeChecks, c.clusterSpec.ContinueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
if err := updateDeploymentAndWait(c.context, deployment, c.store.Namespace, config.RgwType, daemonLetterID, c.skipUpgradeChecks, c.clusterSpec.ContinueUpgradeAfterChecksEvenIfNotHealthy); err != nil {
|
||||
return errors.Wrapf(err, "failed to update object store %q deployment %q", c.store.Name, deployment.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func NewPodSpecTester(t *testing.T, spec *v1.PodSpec) *PodSpecTester {
|
||||
// AssertVolumesMeetCephRequirements asserts that all the required Ceph volumes exist in the pod
|
||||
// spec under test, Volumes list.
|
||||
func (ps *PodSpecTester) AssertVolumesMeetCephRequirements(
|
||||
daemonType config.DaemonType, daemonID string,
|
||||
daemonType, daemonID string,
|
||||
) {
|
||||
// #nosec because of the word `Secret`
|
||||
keyringSecretName := fmt.Sprintf("rook-ceph-%s-%s-keyring", daemonType, daemonID)
|
||||
@@ -117,7 +117,7 @@ func (ps *PodSpecTester) AssertRestartPolicyAlways() {
|
||||
|
||||
// AssertChownContainer ensures that the init container to chown the Ceph data dir is present for
|
||||
// Ceph daemons.
|
||||
func (ps *PodSpecTester) AssertChownContainer(daemonType config.DaemonType) {
|
||||
func (ps *PodSpecTester) AssertChownContainer(daemonType string) {
|
||||
switch daemonType {
|
||||
case config.MonType, config.MgrType, config.OsdType, config.MdsType, config.RgwType, config.RbdMirrorType:
|
||||
assert.True(ps.t, containerExists("chown-container-data-dir", ps.spec))
|
||||
@@ -131,7 +131,7 @@ func (ps *PodSpecTester) AssertPriorityClassNameMatch(name string) {
|
||||
|
||||
// RunFullSuite runs all assertion tests for the PodSpec under test and its sub-resources.
|
||||
func (ps *PodSpecTester) RunFullSuite(
|
||||
daemonType config.DaemonType, resourceName, cephImage,
|
||||
daemonType, resourceName, cephImage,
|
||||
cpuResourceLimit, cpuResourceRequest, memoryResourceLimit, memoryResourceRequest string, priorityClassName string,
|
||||
) {
|
||||
resourceExpectations := optest.ResourceLimitExpectations{
|
||||
|
||||
@@ -19,7 +19,6 @@ package test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rook/rook/pkg/operator/ceph/config"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
@@ -38,7 +37,7 @@ func NewPodTemplateSpecTester(t *testing.T, template *v1.PodTemplateSpec) *PodTe
|
||||
// AssertLabelsContainCephRequirements asserts that the PodTemplateSpec under test contains labels
|
||||
// which all Ceph pods should have.
|
||||
func (pt *PodTemplateSpecTester) AssertLabelsContainCephRequirements(
|
||||
daemonType config.DaemonType, daemonID, appName, namespace string,
|
||||
daemonType, daemonID, appName, namespace string,
|
||||
) {
|
||||
AssertLabelsContainCephRequirements(pt.t, pt.template.ObjectMeta.Labels,
|
||||
daemonType, daemonID, appName, namespace)
|
||||
@@ -46,8 +45,7 @@ func (pt *PodTemplateSpecTester) AssertLabelsContainCephRequirements(
|
||||
|
||||
// RunFullSuite runs all assertion tests for the PodTemplateSpec under test and its sub-resources.
|
||||
func (pt *PodTemplateSpecTester) RunFullSuite(
|
||||
daemonType config.DaemonType,
|
||||
daemonID, appName, namespace, cephImage,
|
||||
daemonType, daemonID, appName, namespace, cephImage,
|
||||
cpuResourceLimit, cpuResourceRequest,
|
||||
memoryResourceLimit, memoryResourceRequest string,
|
||||
priorityClassName string,
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"testing"
|
||||
|
||||
e "github.com/pkg/errors"
|
||||
"github.com/rook/rook/pkg/operator/ceph/config"
|
||||
optest "github.com/rook/rook/pkg/operator/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -78,7 +77,7 @@ func VerifyPodLabels(appName, namespace, daemonType, daemonID string, labels map
|
||||
// DaemonSets, etc.
|
||||
func AssertLabelsContainCephRequirements(
|
||||
t *testing.T, labels map[string]string,
|
||||
daemonType config.DaemonType, daemonID, appName, namespace string,
|
||||
daemonType, daemonID, appName, namespace string,
|
||||
) {
|
||||
optest.AssertLabelsContainRookRequirements(t, labels, appName)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user