diff --git a/.classpath b/.classpath
deleted file mode 100644
index 3bc452ee9e9d1755c11e3e7c24e3ceeaa9a6c4da..0000000000000000000000000000000000000000
--- a/.classpath
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 18308defdbd17a8bd5837fef98d92d04e0014ed4..0000000000000000000000000000000000000000
--- a/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=utf-8
-encoding//src/main/resources=utf-8
-encoding//src/test/java=utf-8
-encoding//src/test/resources=utf-8
-encoding/=UTF-8
diff --git a/docs/20260829_1610_add-crcheckval-state-health-characteristics.md b/docs/20260829_1610_add-crcheckval-state-health-characteristics.md
new file mode 100644
index 0000000000000000000000000000000000000000..615a58aef43a5fd7cd0116110f481d139ce00e30
--- /dev/null
+++ b/docs/20260829_1610_add-crcheckval-state-health-characteristics.md
@@ -0,0 +1,67 @@
+# Add X.731 state + health `_CR_CHECKVAL_` characteristics to KubernetesCRDV1 / KubernetesCRV1
+
+Date: 2026-08-29 16:10
+Branch: `12-update-the-models-to-address-the-new-resource-state-model`
+
+## Why
+
+Issue #12 introduced the ITU-T X.731 resource state model on
+`org.etsi.osl.tmf.ri639.model.Resource`:
+
+- `ResourceAdministrativeStateType` — LOCKED / UNLOCKED / SHUTDOWN
+- `ResourceOperationalStateType` — ENABLE / DISABLE
+- `ResourceUsageStateType` — IDLE / ACTIVE / BUSY
+- `ResourceHealth` (derived) — UP / PENDING / DOWN / HELD / GONE
+
+Until now `KubernetesCRDV1` / `KubernetesCRV1` only let an operator map an observed CR
+status-field value onto a `ResourceStatusType` (`_CR_CHECK_FIELD` +
+`_CR_CHECKVAL_STANDBY|ALARM|AVAILABLE|RESERVED|UNKNOWN|SUSPENDED`). `ResourceStatusType` is now
+inventory/pool bookkeeping only; health lives in the X.731 pair. Operators need the same
+value-mapping mechanism for the new state dimensions.
+
+## What changed
+
+Both `KubernetesCRDV1` and `KubernetesCRV1`:
+
+1. **13 new `String` backing fields** alongside the existing `statusCheckValue*` block:
+ `adminStateCheckValueLocked/Unlocked/Shutdown`, `operStateCheckValueEnable/Disable`,
+ `usageStateCheckValueIdle/Active/Busy`,
+ `healthCheckValueUp/Pending/Down/Held/Gone`. Covered by the class-level Lombok
+ `@Getter`/`@Setter`; not `@Builder` constructor params (same as the existing fields).
+
+2. **13 new spec characteristics** in `toRSpecCreate()`, added after `_CR_CHECKVAL_SUSPENDED`
+ (empty default value, TEXT, non-configurable):
+ `_CR_CHECKVAL_ADMINSTATE_LOCKED`, `_CR_CHECKVAL_ADMINSTATE_UNLOCKED`,
+ `_CR_CHECKVAL_ADMINSTATE_SHUTDOWN`, `_CR_CHECKVAL_OPERSTATE_ENABLE`,
+ `_CR_CHECKVAL_OPERSTATE_DISABLE`, `_CR_CHECKVAL_USAGESTATE_IDLE`,
+ `_CR_CHECKVAL_USAGESTATE_ACTIVE`, `_CR_CHECKVAL_USAGESTATE_BUSY`,
+ `_CR_CHECKVAL_HEALTH_UP`, `_CR_CHECKVAL_HEALTH_PENDING`, `_CR_CHECKVAL_HEALTH_DOWN`,
+ `_CR_CHECKVAL_HEALTH_HELD`, `_CR_CHECKVAL_HEALTH_GONE`.
+
+3. **13 matching resource characteristics** in `toResourceCreate()`, each carrying the value of
+ the corresponding new field.
+
+4. **RSpec version bumps** (spec characteristics changed):
+ - `KubernetesCRDV1.OSL_KUBCRD_RSPEC_VERSION` `0.0.3` → `0.0.4`
+ - `KubernetesCRV1.OSL_KUBCRV1_RSPEC_VERSION` `0.0.4` → `0.0.5`
+
+`toRSpecUpdate()` / `toResourceUpdate()` delegate to the create methods — unchanged.
+
+## Files
+
+- `src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRDV1.java`
+- `src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1.java`
+
+## Verification
+
+- `mvn -q compile` — clean.
+- `grep -c` of the new key prefixes → 26 per file (13 in `toRSpecCreate` + 13 in
+ `toResourceCreate`).
+
+## Follow-up
+
+- `KubernetesCRV1.applyHealth(...)` derived health from `statusValue` only — addressed in
+ [20260829_1619](20260829_1619_kubernetescrv1-applystate-from-x731-not-status.md)
+ (`applyState` now keyed off resolved `healthValue` / `*StateValue` fields).
+- Still open: the cridge watcher must populate those resolved fields from the `_CR_CHECK_FIELD` +
+ `_CR_CHECKVAL_*` mapping.
diff --git a/docs/20260829_1619_kubernetescrv1-applystate-from-x731-not-status.md b/docs/20260829_1619_kubernetescrv1-applystate-from-x731-not-status.md
new file mode 100644
index 0000000000000000000000000000000000000000..a2750e02be5bb9998a39f22399a6ba4d87f631d4
--- /dev/null
+++ b/docs/20260829_1619_kubernetescrv1-applystate-from-x731-not-status.md
@@ -0,0 +1,54 @@
+# KubernetesCRV1: derive resource state from the X.731 model, not from `statusValue`
+
+Date: 2026-08-29 16:19
+Branch: `12-update-the-models-to-address-the-new-resource-state-model`
+
+## Why
+
+`KubernetesCRV1.applyHealth(ResourceCreate)` set `operationalState` / `administrativeState`
+by switching on `statusValue` (`ResourceStatusType`). Per issue #12, `ResourceStatusType` is
+inventory/pool bookkeeping and must not be the source of a health verdict — health belongs to the
+ITU-T X.731 attributes and the derived `ResourceHealth`. The producer (cridge watcher) should
+report state directly rather than have the model reverse-engineer it from a pool-status label.
+
+## What changed
+
+`src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1.java`:
+
+1. **New resolved-value fields** (builder params, alongside `statusValue`):
+ - `ResourceHealth healthValue` — the convenient path
+ - `ResourceOperationalStateType operationalStateValue`
+ - `ResourceAdministrativeStateType administrativeStateValue`
+ - `ResourceUsageStateType usageStateValue`
+
+ These are the producer's *resolved* view. They are distinct from the `*CheckValue*` /
+ `healthCheckValue*` strings added earlier, which are operator mapping *configuration* passed
+ through as characteristics.
+
+2. **`applyHealth(...)` → `applyState(...)`**, rewritten:
+ - `OSL_LABEL_DELETED` short-circuit unchanged (→ `DISABLE` + `SHUTDOWN`).
+ - If `healthValue` is set, translate it to the `operationalState`/`administrativeState` pair
+ exactly as `Resource.applyHealth` does (UP→enable/unlocked, DOWN→disable/unlocked,
+ HELD→enable/locked, GONE→disable/shutdown, PENDING→leave unset) **and return** — the verdict
+ is authoritative.
+ - Only when `healthValue` is null, apply `operationalStateValue` / `administrativeStateValue` /
+ `usageStateValue` individually if set.
+ - No longer reads `statusValue`.
+
+3. `statusValue` is retained — it still drives `rs.resourceStatus(...)` (pool bookkeeping only).
+
+The method was kept (not removed): `ResourceCreate` has no `applyHealth` helper, so the
+`ResourceHealth` → enum-pair translation still needs a home.
+
+## Verification
+
+- `mvn -q compile` — clean.
+- No in-repo callers of `KubernetesCRV1` broke; the Lombok builder gains four optional setters.
+- Existing `_CR_CHECKVAL_*` characteristics and `statusValue`→`resourceStatus` mapping unchanged.
+
+## Follow-up (out of scope)
+
+The cridge watcher must be updated to populate `healthValue` (and/or the individual
+`*StateValue` fields) on the `KubernetesCRV1` it builds, using the `_CR_CHECK_FIELD` +
+`_CR_CHECKVAL_*` mapping now including the new `ADMINSTATE` / `OPERSTATE` / `USAGESTATE` /
+`HEALTH` keys.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..84bde302a54ffb6a48cd78dd21431a7ddda4ea94
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,4 @@
+# Docs index
+
+- [20260829_1610 Add X.731 state + health `_CR_CHECKVAL_` characteristics](20260829_1610_add-crcheckval-state-health-characteristics.md) — new namespaced `_CR_CHECKVAL_ADMINSTATE_*`, `_CR_CHECKVAL_OPERSTATE_*`, `_CR_CHECKVAL_USAGESTATE_*` and `_CR_CHECKVAL_HEALTH_*` characteristics on `KubernetesCRDV1` / `KubernetesCRV1`, plus RSpec version bumps.
+- [20260829_1619 KubernetesCRV1: derive state from X.731, not `statusValue`](20260829_1619_kubernetescrv1-applystate-from-x731-not-status.md) — `applyHealth` → `applyState`, keyed off new `healthValue` / `operationalStateValue` / `administrativeStateValue` / `usageStateValue` builder params instead of the `ResourceStatusType` switch.
diff --git a/pom.xml b/pom.xml
index c2e757edec293341fbce1467759f8aad5c00f12d..e09ab2ec4c78bae296dddc7c7671e2624a5018b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,19 @@
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot-version}
+ pom
+ import
+
+
+
+
org.projectlombok
@@ -50,5 +63,10 @@
org.etsi.osl.model.tmf
${org.etsi.osl.model.tmf.version}
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
\ No newline at end of file
diff --git a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRDV1.java b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRDV1.java
index 35d639254707f6c06c56ea4ee81595e4d559f6ed..5f8ac7a1214838fe4e36519d7ebfadb5ce485b3e 100644
--- a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRDV1.java
+++ b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRDV1.java
@@ -12,6 +12,7 @@ import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationCreate;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationRef;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationUpdate;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.etsi.osl.tmf.ri639.model.ResourceUpdate;
@@ -28,7 +29,7 @@ public class KubernetesCRDV1 extends DomainModelDefinition
implements ITMFRCM634_ModelTransformer, ITMFRI639_ModelTransformer {
public static final String OSL_KUBCRD_RSPEC_NAME = "kubernetes-crd.openslice.io";
- public static final String OSL_KUBCRD_RSPEC_VERSION = "0.0.3";
+ public static final String OSL_KUBCRD_RSPEC_VERSION = "0.0.4";
public static final String OSL_KUBCRD_RSPEC_CATEGORY = "KubernetesCRD-apiextensions.k8s.io/v1";
public static final String OSL_KUBCRD_RESOURCE_CATEGORY = "KubernetesCRD-apiextensions.k8s.io/v1";
public static final String OSL_KUBCRD_RSPEC_TYPE = "LogicalResourceSpecification";
@@ -56,6 +57,20 @@ public class KubernetesCRDV1 extends DomainModelDefinition
private String statusCheckValueUnknown;
private String statusCheckValueSuspended;
+ private String adminStateCheckValueLocked;
+ private String adminStateCheckValueUnlocked;
+ private String adminStateCheckValueShutdown;
+ private String operStateCheckValueEnable;
+ private String operStateCheckValueDisable;
+ private String usageStateCheckValueIdle;
+ private String usageStateCheckValueActive;
+ private String usageStateCheckValueBusy;
+ private String healthCheckValueUp;
+ private String healthCheckValuePending;
+ private String healthCheckValueDown;
+ private String healthCheckValueHeld;
+ private String healthCheckValueGone;
+
@Builder
public KubernetesCRDV1(String osl_KUBCRD_RSPEC_UUID, String uuid, String name, String version, String description, String category,
@@ -126,7 +141,20 @@ public class KubernetesCRDV1 extends DomainModelDefinition
rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_RESERVED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal the reserved status", false);
rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_UNKNOWN", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal the unknown status", false);
rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_SUSPENDED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal the suspended status", false);
-
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_ADMINSTATE_LOCKED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal administrativeState=locked", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_ADMINSTATE_UNLOCKED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal administrativeState=unlocked", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_ADMINSTATE_SHUTDOWN", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal administrativeState=shutdown", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_OPERSTATE_ENABLE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal operationalState=enable", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_OPERSTATE_DISABLE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal operationalState=disable", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_USAGESTATE_IDLE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal usageState=idle", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_USAGESTATE_ACTIVE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal usageState=active", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_USAGESTATE_BUSY", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal usageState=busy", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_UP", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=UP", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_PENDING", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=PENDING", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_DOWN", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=DOWN", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_HELD", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=HELD", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_GONE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=GONE", false);
+
// rsc.addResourceSpecificationCharacteristicItemShort( "properties", "", EValueType.SET.getValue());
// rsc.addResourceSpecificationCharacteristicItemShort( "additionalProperties", "", EValueType.SET.getValue());
if (this.properties != null)
@@ -185,6 +213,7 @@ public class KubernetesCRDV1 extends DomainModelDefinition
.description( this.description )
.resourceStatus( ResourceStatusType.AVAILABLE )
.operationalState( ResourceOperationalStateType.ENABLE )
+ .administrativeState( ResourceAdministrativeStateType.UNLOCKED )
.resourceSpecification( rSpecRef )
.resourceVersion( this.version);
@@ -208,9 +237,22 @@ public class KubernetesCRDV1 extends DomainModelDefinition
rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_RESERVED", this.statusCheckValueReserved, EValueType.TEXT.getValue());
rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_UNKNOWN", this.statusCheckValueUnknown, EValueType.TEXT.getValue());
rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_SUSPENDED", this.statusCheckValueSuspended, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_ADMINSTATE_LOCKED", this.adminStateCheckValueLocked, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_ADMINSTATE_UNLOCKED", this.adminStateCheckValueUnlocked, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_ADMINSTATE_SHUTDOWN", this.adminStateCheckValueShutdown, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_OPERSTATE_ENABLE", this.operStateCheckValueEnable, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_OPERSTATE_DISABLE", this.operStateCheckValueDisable, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_USAGESTATE_IDLE", this.usageStateCheckValueIdle, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_USAGESTATE_ACTIVE", this.usageStateCheckValueActive, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_USAGESTATE_BUSY", this.usageStateCheckValueBusy, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_UP", this.healthCheckValueUp, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_PENDING", this.healthCheckValuePending, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_DOWN", this.healthCheckValueDown, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_HELD", this.healthCheckValueHeld, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_GONE", this.healthCheckValueGone, EValueType.TEXT.getValue());
+
-
if (this.properties != null)
this.properties.forEach((kPropName, vProVal) -> {
diff --git a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1.java b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1.java
index d634df07d59c1a30a125fc9b99c4605f705e44bb..3dd60dd0a173faf282f70712589bf5f616dab2d9 100644
--- a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1.java
+++ b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1.java
@@ -12,9 +12,12 @@ import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationCreate;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationRef;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationUpdate;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
+import org.etsi.osl.tmf.ri639.model.ResourceHealth;
import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.etsi.osl.tmf.ri639.model.ResourceUpdate;
+import org.etsi.osl.tmf.ri639.model.ResourceUsageStateType;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -28,7 +31,10 @@ public class KubernetesCRV1 extends DomainModelDefinition
implements ITMFRCM634_ModelTransformer, ITMFRI639_ModelTransformer {
public static final String OSL_KUBCRV1_RSPEC_NAME = "kubernetes-cr-v1.openslice.io";
- public static final String OSL_KUBCRV1_RSPEC_VERSION = "0.0.4";
+
+ /** Label stamped by the cridge watcher when the CR is deleted from the cluster. */
+ public static final String OSL_LABEL_DELETED = "org.etsi.osl.deleted";
+ public static final String OSL_KUBCRV1_RSPEC_VERSION = "0.0.5";
public static final String OSL_KUBCRV1_RSPEC_CATEGORY = "KubernetesCRV1-apiextensions.k8s.io/v1";
public static final String OSL_KUBCRV1_RSPEC_TYPE = "LogicalResourceSpecification";
public static final String OSL_KUBCRV1_RSPEC_DESCRIPTION = "This Specification is used to describe a generic KubernetesCRV1";
@@ -55,12 +61,39 @@ public class KubernetesCRV1 extends DomainModelDefinition
private String statusCheckValueReserved;
private String statusCheckValueUnknown;
private String statusCheckValueSuspended;
+ private String adminStateCheckValueLocked;
+ private String adminStateCheckValueUnlocked;
+ private String adminStateCheckValueShutdown;
+ private String operStateCheckValueEnable;
+ private String operStateCheckValueDisable;
+ private String usageStateCheckValueIdle;
+ private String usageStateCheckValueActive;
+ private String usageStateCheckValueBusy;
+ private String healthCheckValueUp;
+ private String healthCheckValuePending;
+ private String healthCheckValueDown;
+ private String healthCheckValueHeld;
+ private String healthCheckValueGone;
+
+ /** Pool/inventory bookkeeping only — never health. Drives {@code resourceStatus}. */
private ResourceStatusType statusValue;
+ /**
+ * The producer's resolved ITU-T X.731 view of the CR. {@code healthValue} is the convenient
+ * path (translated to the operationalState/administrativeState pair by {@link #applyState});
+ * the individual attributes, when set, win over only in the case that {@code healthValue} is not specified, otherwise are ignored.
+ */
+ private ResourceHealth healthValue;
+ private ResourceOperationalStateType operationalStateValue;
+ private ResourceAdministrativeStateType administrativeStateValue;
+ private ResourceUsageStateType usageStateValue;
+
@Builder
public KubernetesCRV1(String osl_KUBCRV1_RSPEC_UUID, String uuid, String name, String version, String description, String category,
String clusterMasterURL, String currentContextCluster, String fullResourceName, String namespace,
String kind, String apiGroup, String uID, String metadata, ResourceStatusType statusValue,
+ ResourceHealth healthValue, ResourceOperationalStateType operationalStateValue,
+ ResourceAdministrativeStateType administrativeStateValue, ResourceUsageStateType usageStateValue,
String yaml,
String json) {
super(uuid, name, version, description, category);
@@ -76,6 +109,10 @@ public class KubernetesCRV1 extends DomainModelDefinition
this.yaml = yaml;
this.json = json;
this.statusValue = statusValue;
+ this.healthValue = healthValue;
+ this.operationalStateValue = operationalStateValue;
+ this.administrativeStateValue = administrativeStateValue;
+ this.usageStateValue = usageStateValue;
this.properties = new HashMap<>();
this.additionalProperties = new HashMap<>();
}
@@ -128,7 +165,20 @@ public class KubernetesCRV1 extends DomainModelDefinition
rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_RESERVED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal the reserved status", false);
rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_UNKNOWN", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal the unknown status", false);
rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_SUSPENDED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal the suspended status", false);
-
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_ADMINSTATE_LOCKED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal administrativeState=locked", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_ADMINSTATE_UNLOCKED", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal administrativeState=unlocked", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_ADMINSTATE_SHUTDOWN", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal administrativeState=shutdown", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_OPERSTATE_ENABLE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal operationalState=enable", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_OPERSTATE_DISABLE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal operationalState=disable", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_USAGESTATE_IDLE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal usageState=idle", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_USAGESTATE_ACTIVE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal usageState=active", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_USAGESTATE_BUSY", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal usageState=busy", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_UP", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=UP", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_PENDING", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=PENDING", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_DOWN", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=DOWN", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_HELD", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=HELD", false);
+ rsc.addResourceSpecificationCharacteristicItemShort( "_CR_CHECKVAL_HEALTH_GONE", "", EValueType.TEXT.getValue(), "Used for providing the equivalent value from resource to signal health=GONE", false);
+
// rsc.addResourceSpecificationCharacteristicItemShort( "properties", "", EValueType.SET.getValue());
// rsc.addResourceSpecificationCharacteristicItemShort( "additionalProperties", "", EValueType.SET.getValue());
@@ -166,7 +216,6 @@ public class KubernetesCRV1 extends DomainModelDefinition
.resourceStatus( ResourceStatusType.RESERVED )
- .operationalState( ResourceOperationalStateType.ENABLE )
.resourceSpecification( rSpecRef )
.resourceVersion( this.version);
@@ -191,9 +240,23 @@ public class KubernetesCRV1 extends DomainModelDefinition
rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_RESERVED", this.statusCheckValueReserved, EValueType.TEXT.getValue());
rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_UNKNOWN", this.statusCheckValueUnknown, EValueType.TEXT.getValue());
rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_SUSPENDED", this.statusCheckValueSuspended, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_ADMINSTATE_LOCKED", this.adminStateCheckValueLocked, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_ADMINSTATE_UNLOCKED", this.adminStateCheckValueUnlocked, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_ADMINSTATE_SHUTDOWN", this.adminStateCheckValueShutdown, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_OPERSTATE_ENABLE", this.operStateCheckValueEnable, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_OPERSTATE_DISABLE", this.operStateCheckValueDisable, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_USAGESTATE_IDLE", this.usageStateCheckValueIdle, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_USAGESTATE_ACTIVE", this.usageStateCheckValueActive, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_USAGESTATE_BUSY", this.usageStateCheckValueBusy, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_UP", this.healthCheckValueUp, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_PENDING", this.healthCheckValuePending, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_DOWN", this.healthCheckValueDown, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_HELD", this.healthCheckValueHeld, EValueType.TEXT.getValue());
+ rs.addResourceCharacteristicItemShort("_CR_CHECKVAL_HEALTH_GONE", this.healthCheckValueGone, EValueType.TEXT.getValue());
+
+ rs.resourceStatus( this.statusValue );
+ applyState( rs );
- rs.resourceStatus( this.statusValue );
-
if (this.properties != null)
this.properties.forEach((kPropName, vProVal) -> {
rs.addResourceCharacteristicItemShort(kPropName , vProVal, EValueType.TEXT.getValue());
@@ -210,7 +273,69 @@ public class KubernetesCRV1 extends DomainModelDefinition
return rs;
}
-
+
+ /**
+ * Declares the CR's ITU-T X.731 state attributes on the resource, from what the producer
+ * reported — not from {@code resourceStatus}, which is inventory/pool bookkeeping and
+ * says nothing about whether the CR is working.
+ *
+ *
operationalState / administrativeState are what
+ * {@code Service.findNextStateBasedOnResourceList} folds into the supported service's state;
+ * usageState is orthogonal. The producer may report a {@link ResourceHealth} verdict via
+ * {@code healthValue} — translated here to the operationalState/administrativeState pair the
+ * same way {@code Resource.applyHealth} does, since {@code ResourceCreate} has no such helper.
+ * When {@code healthValue} is set it is authoritative: the individual
+ * {@code operationalStateValue} / {@code administrativeStateValue} / {@code usageStateValue}
+ * attributes are consulted only when no verdict was given. A CR with nothing set is left with
+ * both health attributes unset: that reads as "pending", which neither promotes nor demotes
+ * the service.
+ */
+ private void applyState(ResourceCreate rs) {
+
+ // A CR the watcher saw disappear is gone, whatever else was reported.
+ if (this.properties != null && this.properties.get(OSL_LABEL_DELETED) != null) {
+ rs.operationalState(ResourceOperationalStateType.DISABLE)
+ .administrativeState(ResourceAdministrativeStateType.SHUTDOWN);
+ return;
+ }
+
+ if (this.healthValue != null) {
+ switch (this.healthValue) {
+ case UP:
+ rs.operationalState(ResourceOperationalStateType.ENABLE)
+ .administrativeState(ResourceAdministrativeStateType.UNLOCKED);
+ break;
+ case DOWN:
+ // In service, but not working.
+ rs.operationalState(ResourceOperationalStateType.DISABLE)
+ .administrativeState(ResourceAdministrativeStateType.UNLOCKED);
+ break;
+ case HELD:
+ rs.operationalState(ResourceOperationalStateType.ENABLE)
+ .administrativeState(ResourceAdministrativeStateType.LOCKED);
+ break;
+ case GONE:
+ rs.operationalState(ResourceOperationalStateType.DISABLE)
+ .administrativeState(ResourceAdministrativeStateType.SHUTDOWN);
+ break;
+ default:
+ // PENDING: nothing is known about this resource; leave both attributes unset.
+ break;
+ }
+ return;
+ }
+
+ if (this.operationalStateValue != null) {
+ rs.operationalState(this.operationalStateValue);
+ }
+ if (this.administrativeStateValue != null) {
+ rs.administrativeState(this.administrativeStateValue);
+ }
+ if (this.usageStateValue != null) {
+ rs.usageState(this.usageStateValue);
+ }
+ }
+
@Override
public ResourceUpdate toResourceUpdate() {
diff --git a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesConfigMap.java b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesConfigMap.java
index 2e4457ac7ee6af1f93aa467106a3bce6ff4aa699..80c45c01923d7f9c73c2a9548b30d1c1d98d3dfd 100644
--- a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesConfigMap.java
+++ b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesConfigMap.java
@@ -13,6 +13,7 @@ import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationCreate;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationRef;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationUpdate;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.etsi.osl.tmf.ri639.model.ResourceUpdate;
@@ -166,6 +167,7 @@ public class KubernetesConfigMap extends DomainModelDefinition
.description( this.description )
.resourceStatus( ResourceStatusType.AVAILABLE )
.operationalState( ResourceOperationalStateType.ENABLE )
+ .administrativeState( ResourceAdministrativeStateType.UNLOCKED )
.resourceSpecification( rSpecRef )
.resourceVersion( this.version);
diff --git a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesContextDefinition.java b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesContextDefinition.java
index c4f6a01987e8efe01f00b13e345b202ddda564f8..57d5f5fa88b598f1fac4ec8d2ed5042e6bbeb692 100644
--- a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesContextDefinition.java
+++ b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesContextDefinition.java
@@ -11,6 +11,7 @@ import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationRef;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationUpdate;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.etsi.osl.tmf.ri639.model.ResourceUpdate;
import lombok.Builder;
@@ -127,7 +128,8 @@ public class KubernetesContextDefinition
ResourceCreate rs = new ResourceCreate();
rs.setName( this.name );
rs.setResourceStatus( ResourceStatusType.AVAILABLE );
- rs.setOperationalState( ResourceOperationalStateType.ENABLE);
+ rs.setOperationalState( ResourceOperationalStateType.ENABLE );
+ rs.setAdministrativeState( ResourceAdministrativeStateType.UNLOCKED );
rs.setCategory( OSL_KUBD_RESOURCE_CATEGORY );
rs.setResourceSpecification(rSpecRef);
rs.resourceVersion( this.clusterVersion );
diff --git a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesSecret.java b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesSecret.java
index 1091d7faf585da0d612c3faabeac23d8b4f84d9f..f6d8765823152b6de4b118fa7f8e59f8e59618e3 100644
--- a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesSecret.java
+++ b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesSecret.java
@@ -12,6 +12,7 @@ import org.etsi.osl.tmf.rcm634.model.ResourceSpecification;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationCreate;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationRef;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationUpdate;
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
@@ -166,6 +167,7 @@ public class KubernetesSecret extends DomainModelDefinition
.description( this.description )
.resourceStatus( ResourceStatusType.AVAILABLE )
.operationalState( ResourceOperationalStateType.ENABLE )
+ .administrativeState( ResourceAdministrativeStateType.UNLOCKED )
.resourceSpecification( rSpecRef )
.resourceVersion( this.version);
diff --git a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesService.java b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesService.java
index 38fa5cead4d89a431c9976281e900c23d22e0284..1a5b06d499bf82937d02c64e63a7aeba2dade074 100644
--- a/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesService.java
+++ b/src/main/java/org/etsi/osl/domain/model/kubernetes/KubernetesService.java
@@ -13,6 +13,7 @@ import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationCreate;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationRef;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecificationUpdate;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.etsi.osl.tmf.ri639.model.ResourceUpdate;
@@ -181,6 +182,7 @@ public class KubernetesService extends DomainModelDefinition
.description( this.description )
.resourceStatus( ResourceStatusType.AVAILABLE )
.operationalState( ResourceOperationalStateType.ENABLE )
+ .administrativeState( ResourceAdministrativeStateType.UNLOCKED )
.resourceSpecification( rSpecRef )
.resourceVersion( this.version);
diff --git a/src/test/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1ApplyStateTest.java b/src/test/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1ApplyStateTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..21f80569d9c6e45460ef79d5b529191e39b68c2a
--- /dev/null
+++ b/src/test/java/org/etsi/osl/domain/model/kubernetes/KubernetesCRV1ApplyStateTest.java
@@ -0,0 +1,171 @@
+package org.etsi.osl.domain.model.kubernetes;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType;
+import org.etsi.osl.tmf.ri639.model.ResourceCreate;
+import org.etsi.osl.tmf.ri639.model.ResourceHealth;
+import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType;
+import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
+import org.etsi.osl.tmf.ri639.model.ResourceUpdate;
+import org.etsi.osl.tmf.ri639.model.ResourceUsageStateType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+/**
+ * The ITU-T X.731 state that {@link KubernetesCRV1#toResourceCreate()} declares on the resource,
+ * from what the producer (the cridge watcher) reported.
+ *
+ *
Two contracts have to hold at once:
+ *
+ *
+ * - the operationalState/administrativeState pair is spelled out exactly as
+ * {@code Resource.applyHealth} spells it, so that {@code Resource.deriveHealth()} on the inventory
+ * side reads back the verdict the producer gave — that pair is what
+ * {@code Service.findNextStateBasedOnResourceList} folds into the service state;
+ *
- {@code resourceStatus} (pool bookkeeping) and the state attributes are independent: neither
+ * one is derived from the other.
+ *
+ */
+public class KubernetesCRV1ApplyStateTest {
+
+ private static KubernetesCRV1.KubernetesCRV1Builder aCR() {
+ // the spec UUID is the only thing toResourceCreate() refuses to work without
+ return KubernetesCRV1.builder().osl_KUBCRV1_RSPEC_UUID("rspec-uuid").name("a-cr");
+ }
+
+ private static void assertState(ResourceUpdate rs,
+ ResourceOperationalStateType oper, ResourceAdministrativeStateType admin) {
+ assertEquals(oper, rs.getOperationalState(), "operationalState");
+ assertEquals(admin, rs.getAdministrativeState(), "administrativeState");
+ }
+
+ /**
+ * A CR whose producer said nothing about its state. Both attributes stay unset: on the inventory
+ * side that derives as {@code PENDING}, which neither promotes nor demotes the supported service.
+ * In particular there is no {@code operationalState=ENABLE} default any more — that used to be
+ * declared unconditionally, and with {@code administrativeState} unset it was PENDING anyway.
+ */
+ @Test
+ public void nothingReportedLeavesBothAttributesUnset() {
+ ResourceCreate rs = aCR().build().toResourceCreate();
+
+ assertState(rs, null, null);
+ assertNull(rs.getUsageState());
+ }
+
+ /** The {@code healthValue} → attribute-pair translation, verdict by verdict. */
+ @ParameterizedTest(name = "health={0} -> oper={1}, admin={2}")
+ @CsvSource({
+ "UP, ENABLE, UNLOCKED",
+ "DOWN, DISABLE, UNLOCKED",
+ "HELD, ENABLE, LOCKED",
+ "GONE, DISABLE, SHUTDOWN",
+ "PENDING, , ",
+ })
+ public void healthValueIsSpelledOutAsTheX731Pair(ResourceHealth health,
+ ResourceOperationalStateType expectedOper, ResourceAdministrativeStateType expectedAdmin) {
+ ResourceCreate rs = aCR().healthValue(health).build().toResourceCreate();
+
+ assertState(rs, expectedOper, expectedAdmin);
+ }
+
+ /**
+ * When no verdict is given the producer may still report the attributes one by one; each is
+ * applied on its own and the others are left untouched.
+ */
+ @Test
+ public void individualAttributesApplyWhenNoVerdictWasGiven() {
+ ResourceCreate operOnly = aCR()
+ .operationalStateValue(ResourceOperationalStateType.ENABLE)
+ .build().toResourceCreate();
+ assertState(operOnly, ResourceOperationalStateType.ENABLE, null);
+
+ ResourceCreate all = aCR()
+ .operationalStateValue(ResourceOperationalStateType.DISABLE)
+ .administrativeStateValue(ResourceAdministrativeStateType.LOCKED)
+ .usageStateValue(ResourceUsageStateType.BUSY)
+ .build().toResourceCreate();
+ assertState(all, ResourceOperationalStateType.DISABLE, ResourceAdministrativeStateType.LOCKED);
+ assertEquals(ResourceUsageStateType.BUSY, all.getUsageState());
+ }
+
+ /**
+ * A verdict is authoritative: individual operational/administrative values reported alongside
+ * it do not override the pair it implies — they are consulted only when no
+ * {@code healthValue} was given.
+ */
+ @Test
+ public void healthValueWinsOverIndividualOperAndAdminValues() {
+ ResourceCreate rs = aCR()
+ .healthValue(ResourceHealth.UP)
+ .operationalStateValue(ResourceOperationalStateType.DISABLE)
+ .administrativeStateValue(ResourceAdministrativeStateType.LOCKED)
+ .build().toResourceCreate();
+
+ assertState(rs, ResourceOperationalStateType.ENABLE, ResourceAdministrativeStateType.UNLOCKED);
+ }
+
+ /**
+ * The same precedence covers {@code usageStateValue}: once a verdict is given the individual
+ * attributes are ignored wholesale, so usage state is left unset alongside them. A producer that
+ * wants to report usage state must report the operational/administrative attributes individually
+ * instead of a {@code healthValue}.
+ */
+ @Test
+ public void usageStateIsIgnoredWhenAVerdictWasGiven() {
+ ResourceCreate rs = aCR()
+ .healthValue(ResourceHealth.UP)
+ .usageStateValue(ResourceUsageStateType.BUSY)
+ .build().toResourceCreate();
+
+ assertState(rs, ResourceOperationalStateType.ENABLE, ResourceAdministrativeStateType.UNLOCKED);
+ assertNull(rs.getUsageState(), "usageState");
+ }
+
+ /**
+ * The watcher stamps {@link KubernetesCRV1#OSL_LABEL_DELETED} on a CR it saw disappear from the
+ * cluster. That is GONE whatever else was reported, including a stale UP verdict.
+ */
+ @Test
+ public void deletedLabelIsGoneWhateverElseWasReported() {
+ KubernetesCRV1 cr = aCR()
+ .healthValue(ResourceHealth.UP)
+ .operationalStateValue(ResourceOperationalStateType.ENABLE)
+ .administrativeStateValue(ResourceAdministrativeStateType.UNLOCKED)
+ .build();
+ cr.getProperties().put(KubernetesCRV1.OSL_LABEL_DELETED, "DELETED");
+
+ ResourceCreate rs = cr.toResourceCreate();
+
+ assertState(rs, ResourceOperationalStateType.DISABLE, ResourceAdministrativeStateType.SHUTDOWN);
+ }
+
+ /**
+ * {@code statusValue} is pool bookkeeping. It is passed through to {@code resourceStatus} and
+ * must play no part in the health attributes — an {@code ALARM} does not make the CR DOWN, and an
+ * {@code AVAILABLE} does not make it UP.
+ */
+ @ParameterizedTest(name = "resourceStatus={0} does not touch health")
+ @CsvSource({"ALARM", "AVAILABLE", "RESERVED", "UNKNOWN"})
+ public void resourceStatusIsPassedThroughAndNeverDrivesHealth(ResourceStatusType status) {
+ ResourceCreate silent = aCR().statusValue(status).build().toResourceCreate();
+ assertEquals(status, silent.getResourceStatus());
+ assertState(silent, null, null);
+
+ ResourceCreate up = aCR().statusValue(status).healthValue(ResourceHealth.UP)
+ .build().toResourceCreate();
+ assertEquals(status, up.getResourceStatus());
+ assertState(up, ResourceOperationalStateType.ENABLE, ResourceAdministrativeStateType.UNLOCKED);
+ }
+
+ /** The update path is the create path; the watcher uses both depending on what it already knows. */
+ @Test
+ public void toResourceUpdateDeclaresTheSameState() {
+ ResourceUpdate rs = aCR().healthValue(ResourceHealth.DOWN).build().toResourceUpdate();
+
+ assertState(rs, ResourceOperationalStateType.DISABLE, ResourceAdministrativeStateType.UNLOCKED);
+ }
+}
diff --git a/target/.gitignore b/target/.gitignore
deleted file mode 100644
index 840e7d3120ee3206168d49bf62df2c269c38e17e..0000000000000000000000000000000000000000
--- a/target/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/classes/