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: + * + *

+ */ +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/