Loading pom.xml +1 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ <springdoc.security.version>1.7.0</springdoc.security.version> <springdoc.openapiui.version>1.7.0</springdoc.openapiui.version> <keycloak.version>22.0.1</keycloak.version> <org.etsi.osl.model.tmf.version>1.1.0-SNAPSHOT</org.etsi.osl.model.tmf.version> <org.etsi.osl.model.tmf.version>1.4.0-SNAPSHOT</org.etsi.osl.model.tmf.version> <org.etsi.osl.model.k8s.version>1.1.2-SNAPSHOT</org.etsi.osl.model.k8s.version> <fabric8.version>7.3.1</fabric8.version> </properties> Loading src/main/java/org/etsi/osl/controllers/giter/api/CatalogClient.java +32 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,38 @@ public class CatalogClient extends RouteBuilder{ } /** * Reads a resource back from the inventory. * * <p>Used before this controller reports a status, so it can tell an allocation someone else made * ({@code RESERVED}, {@code STANDBY}) from a condition it may restate - see * {@link ResourceUpdate#declareResourceStatus}. A {@code ResourceUpdate} this controller builds * from scratch carries no incumbent value, so there would otherwise be nothing for that guard to * protect. * * @return the live resource, or null if it could not be read; a caller must treat null as * "no allocation known", not as "no allocation exists" */ public Resource retrieveResource(String resourceId) { logger.debug("will retrieve Resource id=" + resourceId ); try { Object response = template.requestBody( CATALOG_GET_RESOURCE_BY_ID, resourceId); if ( !(response instanceof String)) { logger.error("Resource object is wrong."); return null; } //resolves to Logical or Physical through the "@type" discriminator on Resource return toJsonObj( (String)response, Resource.class); }catch (Exception e) { logger.error("Cannot retrieve Resource details from catalog. " + e.toString()); } return null; } public LogicalResourceSpecification createOrUpdateResourceSpecByNameCategoryVersion( ResourceSpecificationCreate s) { logger.debug("will createOrUpdateResourceSpecByNameCategoryVersion " ); logger.debug("s= " + s ); Loading src/main/java/org/etsi/osl/controllers/giter/config/ProducerProperties.java +55 −5 Original line number Diff line number Diff line Loading @@ -101,6 +101,8 @@ public class ProducerProperties { * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_CR_STATUS=Running * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_RESOURCE_STATUS_TYPE=AVAILABLE * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_OPERATIONAL_STATE_TYPE=ENABLE * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_ADMINISTRATIVE_STATE_TYPE=UNLOCKED * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_USAGE_STATE_TYPE=ACTIVE */ private List<StatusMapping> statusMappings = new java.util.ArrayList<>(); Loading @@ -110,13 +112,41 @@ public class ProducerProperties { private String defaultResourceStatusType = "UNKNOWN"; /** * Default ResourceOperationalStateType when no mapping is found * Default ResourceOperationalStateType when no mapping is found. * * <p>Unset by default, and deliberately so. Together with an unset * {@link #defaultAdministrativeStateType} it makes an unrecognised CR phase derive * {@code PENDING}, which neither promotes nor demotes the supporting service. Naming a * verdict here means claiming to know the health of a phase this controller does not * understand - a wrong DISABLE demotes a running service on nothing more than a typo in a * CR. */ private String defaultOperationalStateType; /** * Default ResourceAdministrativeStateType when no mapping is found. Unset by default - see * {@link #defaultOperationalStateType}. */ private String defaultOperationalStateType = "DISABLE"; private String defaultAdministrativeStateType; /** * Default ResourceUsageStateType when no mapping is found. Informational only; it takes no * part in the health verdict. */ private String defaultUsageStateType = "IDLE"; } /** * Mapping configuration for a single CR status value * Mapping configuration for a single CR status value. * * <p>A CR phase is mapped onto two orthogonal axes. The ITU-T X.731 pair * {@code operationalStateType}/{@code administrativeStateType} is the health verdict OSOM reads * to drive the supporting service's lifecycle, and this controller reports it unconditionally. * {@code resourceStatusType} is pool bookkeeping and is only ever <i>declared</i>. * * <p>Leaving a field out of a matched mapping means "write nothing for this attribute", which * is distinct from no mapping matching at all (which falls back to the {@code default-*} * properties). Omitting both X.731 attributes is how a phase reports {@code PENDING}. */ @Data public static class StatusMapping { Loading @@ -126,13 +156,33 @@ public class ProducerProperties { private String crStatus; /** * TMF ResourceStatusType: STANDBY, SUSPEND, AVAILABLE, RESERVED, UNKNOWN * TMF ResourceStatusType: ALARM, AVAILABLE, SUSPENDED, UNKNOWN. * * <p>RESERVED and STANDBY are rejected at startup. Those two are <b>allocations</b> - a * record that someone took the resource out of the free pool - and only whoever made that * claim may write or retract it. This controller observes a deployment; it allocates * nothing. Leave the field out for a phase that says nothing about the resource's * condition. */ private String resourceStatusType; /** * TMF ResourceOperationalStateType: ENABLE, DISABLE * TMF ResourceOperationalStateType: ENABLE, DISABLE. Half of the health verdict; omit it, * along with administrativeStateType, to report PENDING. */ private String operationalStateType; /** * TMF ResourceAdministrativeStateType: LOCKED, UNLOCKED, SHUTDOWN. The other half of the * health verdict - without it {@code Resource.deriveHealth()} can only ever return PENDING, * whatever operationalStateType says. */ private String administrativeStateType; /** * TMF ResourceUsageStateType: IDLE, ACTIVE, BUSY. Informational; not consulted for health, * since a healthy resource carrying no load is legitimately IDLE. */ private String usageStateType; } } src/main/java/org/etsi/osl/controllers/giter/service/ResourceHealthSupport.java 0 → 100644 +78 −0 Original line number Diff line number Diff line package org.etsi.osl.controllers.giter.service; import org.etsi.osl.tmf.ri639.model.Resource; import org.etsi.osl.tmf.ri639.model.ResourceHealth; import org.etsi.osl.tmf.ri639.model.ResourceUpdate; import org.etsi.osl.tmf.ri639.model.ResourceUsageStateType; /** * The ITU-T X.731 health this controller reports on every path that writes a resource back to the * TMF catalog. * * <p>This is the one thing every write path must do. OSOM decides a supporting service's lifecycle * from {@link Resource#deriveHealth()} - the pair {@code operationalState}/{@code administrativeState} * - and never from {@code resourceStatus}, which is pool bookkeeping and moves nothing on its own. * A resource that reports neither attribute derives {@link ResourceHealth#PENDING}, which neither * promotes nor demotes its service: the order simply never finishes, with no exception and nothing * in any log. Reporting only {@code operationalState} - as this controller did before - is the same * failure, because {@code deriveHealth} needs both. * * <p>{@link ResourceUpdate} is not a {@link Resource} and so has no {@code applyHealth} of its own. * The mapping is borrowed from a throwaway {@code Resource} rather than re-tabulated here: * {@code Resource.applyHealth} is the model's single source of truth for how a verdict is spelled * out in attributes, and a hand-written second copy is exactly what drifts. The sibling * {@code org.etsi.osl.controllers.capif.invoker} controller borrows it the same way. * * @author ctranoris */ final class ResourceHealthSupport { private ResourceHealthSupport() { } /** * Spells a health verdict out onto an update as the X.731 attribute pair, plus the informational * usage state. * * <p>{@code resourceStatus} is deliberately untouched: health and pool allocation are orthogonal * axes, and only whoever took a resource out of the pool may put it back. Declare that separately * through {@link ResourceUpdate#declareResourceStatus}. * * <p>Note what {@link ResourceHealth#PENDING} means on the wire. It clears both attributes, and * {@code CatalogClient.updateResourceById} serialises with {@code JsonInclude.NON_NULL}, so the * two nulls are omitted rather than written - the catalog keeps whatever it last knew. PENDING is * therefore "we have nothing to say", not "reset this to unknown". That is right for a resource * that has never reported, and imprecise for one that reported {@code UP} and has gone back to * provisioning; it keeps the stale verdict until it reports again. * * @param resourceUpdate the update about to be written back to the TMF catalog * @param health the verdict OSOM should read back; ignored when null * @param usageState utilisation; informational only, it takes no part in the verdict */ static void applyHealth(ResourceUpdate resourceUpdate, ResourceHealth health, ResourceUsageStateType usageState) { Resource spelledOut = new Resource(); spelledOut.applyHealth(health); resourceUpdate.setOperationalState(spelledOut.getOperationalState()); resourceUpdate.setAdministrativeState(spelledOut.getAdministrativeState()); resourceUpdate.setUsageState(usageState); } /** * Reads back the verdict an update carries, exactly as OSOM will read it off the live resource. * * <p>Used to decide whether reporting this update ends the watch: only {@link ResourceHealth#GONE} * is terminal. Folding through the model rather than testing the attributes by hand keeps the one * definition of "torn down" in {@code Resource.deriveHealth}. * * @param resourceUpdate the update whose X.731 attributes to fold * @return the verdict; never null */ static ResourceHealth healthOf(ResourceUpdate resourceUpdate) { Resource asRead = new Resource(); asRead.setOperationalState(resourceUpdate.getOperationalState()); asRead.setAdministrativeState(resourceUpdate.getAdministrativeState()); return asRead.deriveHealth(); } } src/main/java/org/etsi/osl/controllers/giter/service/ResourceRepoService.java +47 −3 Original line number Diff line number Diff line Loading @@ -17,8 +17,10 @@ import org.etsi.osl.tmf.common.model.service.Note; import org.etsi.osl.tmf.ri639.model.Characteristic; import org.etsi.osl.tmf.ri639.model.Resource; import org.etsi.osl.tmf.ri639.model.ResourceCreate; import org.etsi.osl.tmf.ri639.model.ResourceHealth; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; Loading Loading @@ -144,11 +146,16 @@ public class ResourceRepoService { } catch (SchemaValidationException e) { log.error("{} operation failed", operationType, e); e.printStackTrace(); reportFailure(resourceUpdate, operationType, e.getMessage()); } catch (IOException e) { log.error("{} operation failed", operationType, e); e.printStackTrace(); reportFailure(resourceUpdate, operationType, e.getMessage()); } } else { // No _GITER_SPEC to deploy, so nothing was written to Git and nothing will ever reconcile. // Saying so is the difference between an order that fails and an order that hangs. reportFailure(resourceUpdate, operationType, "no _GITER_SPEC characteristic found on the resource"); } noteItem.author("GITER"); Loading @@ -162,6 +169,33 @@ public class ResourceRepoService { return res; } /** * Reports a CREATE/UPDATE that did not reach Git, so the order does not hang on it. * * <p>These paths used to return with no state written at all. That reads as * {@code ResourceHealth.PENDING}, which OSOM treats as "still coming up" and neither promotes nor * demotes on - so a resource whose CR never got deployed pins its service at RESERVED forever, * with no exception and nothing in any log to say why. DOWN is the honest verdict: administratively * in service ({@code UNLOCKED}) but not working ({@code DISABLE}). * * <p>{@code ALARM} is <i>declared</i>, not set. Unlike the status watcher, this update was built by * OSOM from the live catalog entry, so an orchestrator's {@code RESERVED} is already on the object * and {@code declareResourceStatus} has something to guard - no read-back is needed here. A * failure to deploy is still no reason to release someone else's reservation; health carries the * bad news regardless. */ private void reportFailure(ResourceUpdate resourceUpdate, OperationType operationType, String reason) { String message = String.format("%s failed: %s", operationType, reason); resourceUpdate.declareResourceStatus(ResourceStatusType.ALARM); ResourceHealthSupport.applyHealth(resourceUpdate, ResourceHealth.DOWN, ResourceUsageStateType.IDLE); resourceUpdate.addResourceCharacteristicItemShort("status.infoMessage", message, EValueType.TEXT.getValue()); resourceUpdate.addResourceCharacteristicItemShort("status.Health", ResourceHealth.DOWN.name(), EValueType.TEXT.getValue()); } /** * Extract Custom Resource from _GITER_SPEC characteristic */ Loading Loading @@ -351,10 +385,20 @@ public class ResourceRepoService { log.warn("No CR file path found for resource: {}", resourceid); } //set, not declare: teardown is the one path that legitimately ends an allocation. Everywhere //else this controller only observes and must leave a RESERVED/STANDBY hold to whoever made it //(see StatusWatcherService.seedAllocation), but the resource this one held is gone. resourceUpdate.setResourceStatus(ResourceStatusType.UNKNOWN); //GONE is the terminal X.731 verdict: DISABLE + SHUTDOWN. Without it the deleted resource keeps //whatever health it last reported, so deriveHealth still says UP and the supporting service //never reaches TERMINATED - the CR is archived out of Git and the service stays active on a //resource that no longer exists. Clearing resourceStatus alone does not do this: OSOM reads the //X.731 pair and ignores resourceStatus entirely. ResourceHealthSupport.applyHealth(resourceUpdate, ResourceHealth.GONE, ResourceUsageStateType.IDLE); resourceUpdate.addResourceCharacteristicItemShort("status.infoMessage", "Resource deleted", EValueType.TEXT.getValue()); resourceUpdate.addResourceCharacteristicItemShort("status.Health", "Deleted " + new Date(), EValueType.TEXT.getValue()); resourceUpdate.addResourceCharacteristicItemShort("status.Health", ResourceHealth.GONE.name() + " " + new Date(), EValueType.TEXT.getValue()); noteItem.author("GITER"); noteItem.setText(messages.toString()); Loading Loading
pom.xml +1 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ <springdoc.security.version>1.7.0</springdoc.security.version> <springdoc.openapiui.version>1.7.0</springdoc.openapiui.version> <keycloak.version>22.0.1</keycloak.version> <org.etsi.osl.model.tmf.version>1.1.0-SNAPSHOT</org.etsi.osl.model.tmf.version> <org.etsi.osl.model.tmf.version>1.4.0-SNAPSHOT</org.etsi.osl.model.tmf.version> <org.etsi.osl.model.k8s.version>1.1.2-SNAPSHOT</org.etsi.osl.model.k8s.version> <fabric8.version>7.3.1</fabric8.version> </properties> Loading
src/main/java/org/etsi/osl/controllers/giter/api/CatalogClient.java +32 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,38 @@ public class CatalogClient extends RouteBuilder{ } /** * Reads a resource back from the inventory. * * <p>Used before this controller reports a status, so it can tell an allocation someone else made * ({@code RESERVED}, {@code STANDBY}) from a condition it may restate - see * {@link ResourceUpdate#declareResourceStatus}. A {@code ResourceUpdate} this controller builds * from scratch carries no incumbent value, so there would otherwise be nothing for that guard to * protect. * * @return the live resource, or null if it could not be read; a caller must treat null as * "no allocation known", not as "no allocation exists" */ public Resource retrieveResource(String resourceId) { logger.debug("will retrieve Resource id=" + resourceId ); try { Object response = template.requestBody( CATALOG_GET_RESOURCE_BY_ID, resourceId); if ( !(response instanceof String)) { logger.error("Resource object is wrong."); return null; } //resolves to Logical or Physical through the "@type" discriminator on Resource return toJsonObj( (String)response, Resource.class); }catch (Exception e) { logger.error("Cannot retrieve Resource details from catalog. " + e.toString()); } return null; } public LogicalResourceSpecification createOrUpdateResourceSpecByNameCategoryVersion( ResourceSpecificationCreate s) { logger.debug("will createOrUpdateResourceSpecByNameCategoryVersion " ); logger.debug("s= " + s ); Loading
src/main/java/org/etsi/osl/controllers/giter/config/ProducerProperties.java +55 −5 Original line number Diff line number Diff line Loading @@ -101,6 +101,8 @@ public class ProducerProperties { * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_CR_STATUS=Running * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_RESOURCE_STATUS_TYPE=AVAILABLE * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_OPERATIONAL_STATE_TYPE=ENABLE * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_ADMINISTRATIVE_STATE_TYPE=UNLOCKED * PRODUCER_STATUS_WATCHER_STATUS_MAPPINGS_0_USAGE_STATE_TYPE=ACTIVE */ private List<StatusMapping> statusMappings = new java.util.ArrayList<>(); Loading @@ -110,13 +112,41 @@ public class ProducerProperties { private String defaultResourceStatusType = "UNKNOWN"; /** * Default ResourceOperationalStateType when no mapping is found * Default ResourceOperationalStateType when no mapping is found. * * <p>Unset by default, and deliberately so. Together with an unset * {@link #defaultAdministrativeStateType} it makes an unrecognised CR phase derive * {@code PENDING}, which neither promotes nor demotes the supporting service. Naming a * verdict here means claiming to know the health of a phase this controller does not * understand - a wrong DISABLE demotes a running service on nothing more than a typo in a * CR. */ private String defaultOperationalStateType; /** * Default ResourceAdministrativeStateType when no mapping is found. Unset by default - see * {@link #defaultOperationalStateType}. */ private String defaultOperationalStateType = "DISABLE"; private String defaultAdministrativeStateType; /** * Default ResourceUsageStateType when no mapping is found. Informational only; it takes no * part in the health verdict. */ private String defaultUsageStateType = "IDLE"; } /** * Mapping configuration for a single CR status value * Mapping configuration for a single CR status value. * * <p>A CR phase is mapped onto two orthogonal axes. The ITU-T X.731 pair * {@code operationalStateType}/{@code administrativeStateType} is the health verdict OSOM reads * to drive the supporting service's lifecycle, and this controller reports it unconditionally. * {@code resourceStatusType} is pool bookkeeping and is only ever <i>declared</i>. * * <p>Leaving a field out of a matched mapping means "write nothing for this attribute", which * is distinct from no mapping matching at all (which falls back to the {@code default-*} * properties). Omitting both X.731 attributes is how a phase reports {@code PENDING}. */ @Data public static class StatusMapping { Loading @@ -126,13 +156,33 @@ public class ProducerProperties { private String crStatus; /** * TMF ResourceStatusType: STANDBY, SUSPEND, AVAILABLE, RESERVED, UNKNOWN * TMF ResourceStatusType: ALARM, AVAILABLE, SUSPENDED, UNKNOWN. * * <p>RESERVED and STANDBY are rejected at startup. Those two are <b>allocations</b> - a * record that someone took the resource out of the free pool - and only whoever made that * claim may write or retract it. This controller observes a deployment; it allocates * nothing. Leave the field out for a phase that says nothing about the resource's * condition. */ private String resourceStatusType; /** * TMF ResourceOperationalStateType: ENABLE, DISABLE * TMF ResourceOperationalStateType: ENABLE, DISABLE. Half of the health verdict; omit it, * along with administrativeStateType, to report PENDING. */ private String operationalStateType; /** * TMF ResourceAdministrativeStateType: LOCKED, UNLOCKED, SHUTDOWN. The other half of the * health verdict - without it {@code Resource.deriveHealth()} can only ever return PENDING, * whatever operationalStateType says. */ private String administrativeStateType; /** * TMF ResourceUsageStateType: IDLE, ACTIVE, BUSY. Informational; not consulted for health, * since a healthy resource carrying no load is legitimately IDLE. */ private String usageStateType; } }
src/main/java/org/etsi/osl/controllers/giter/service/ResourceHealthSupport.java 0 → 100644 +78 −0 Original line number Diff line number Diff line package org.etsi.osl.controllers.giter.service; import org.etsi.osl.tmf.ri639.model.Resource; import org.etsi.osl.tmf.ri639.model.ResourceHealth; import org.etsi.osl.tmf.ri639.model.ResourceUpdate; import org.etsi.osl.tmf.ri639.model.ResourceUsageStateType; /** * The ITU-T X.731 health this controller reports on every path that writes a resource back to the * TMF catalog. * * <p>This is the one thing every write path must do. OSOM decides a supporting service's lifecycle * from {@link Resource#deriveHealth()} - the pair {@code operationalState}/{@code administrativeState} * - and never from {@code resourceStatus}, which is pool bookkeeping and moves nothing on its own. * A resource that reports neither attribute derives {@link ResourceHealth#PENDING}, which neither * promotes nor demotes its service: the order simply never finishes, with no exception and nothing * in any log. Reporting only {@code operationalState} - as this controller did before - is the same * failure, because {@code deriveHealth} needs both. * * <p>{@link ResourceUpdate} is not a {@link Resource} and so has no {@code applyHealth} of its own. * The mapping is borrowed from a throwaway {@code Resource} rather than re-tabulated here: * {@code Resource.applyHealth} is the model's single source of truth for how a verdict is spelled * out in attributes, and a hand-written second copy is exactly what drifts. The sibling * {@code org.etsi.osl.controllers.capif.invoker} controller borrows it the same way. * * @author ctranoris */ final class ResourceHealthSupport { private ResourceHealthSupport() { } /** * Spells a health verdict out onto an update as the X.731 attribute pair, plus the informational * usage state. * * <p>{@code resourceStatus} is deliberately untouched: health and pool allocation are orthogonal * axes, and only whoever took a resource out of the pool may put it back. Declare that separately * through {@link ResourceUpdate#declareResourceStatus}. * * <p>Note what {@link ResourceHealth#PENDING} means on the wire. It clears both attributes, and * {@code CatalogClient.updateResourceById} serialises with {@code JsonInclude.NON_NULL}, so the * two nulls are omitted rather than written - the catalog keeps whatever it last knew. PENDING is * therefore "we have nothing to say", not "reset this to unknown". That is right for a resource * that has never reported, and imprecise for one that reported {@code UP} and has gone back to * provisioning; it keeps the stale verdict until it reports again. * * @param resourceUpdate the update about to be written back to the TMF catalog * @param health the verdict OSOM should read back; ignored when null * @param usageState utilisation; informational only, it takes no part in the verdict */ static void applyHealth(ResourceUpdate resourceUpdate, ResourceHealth health, ResourceUsageStateType usageState) { Resource spelledOut = new Resource(); spelledOut.applyHealth(health); resourceUpdate.setOperationalState(spelledOut.getOperationalState()); resourceUpdate.setAdministrativeState(spelledOut.getAdministrativeState()); resourceUpdate.setUsageState(usageState); } /** * Reads back the verdict an update carries, exactly as OSOM will read it off the live resource. * * <p>Used to decide whether reporting this update ends the watch: only {@link ResourceHealth#GONE} * is terminal. Folding through the model rather than testing the attributes by hand keeps the one * definition of "torn down" in {@code Resource.deriveHealth}. * * @param resourceUpdate the update whose X.731 attributes to fold * @return the verdict; never null */ static ResourceHealth healthOf(ResourceUpdate resourceUpdate) { Resource asRead = new Resource(); asRead.setOperationalState(resourceUpdate.getOperationalState()); asRead.setAdministrativeState(resourceUpdate.getAdministrativeState()); return asRead.deriveHealth(); } }
src/main/java/org/etsi/osl/controllers/giter/service/ResourceRepoService.java +47 −3 Original line number Diff line number Diff line Loading @@ -17,8 +17,10 @@ import org.etsi.osl.tmf.common.model.service.Note; import org.etsi.osl.tmf.ri639.model.Characteristic; import org.etsi.osl.tmf.ri639.model.Resource; import org.etsi.osl.tmf.ri639.model.ResourceCreate; import org.etsi.osl.tmf.ri639.model.ResourceHealth; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; Loading Loading @@ -144,11 +146,16 @@ public class ResourceRepoService { } catch (SchemaValidationException e) { log.error("{} operation failed", operationType, e); e.printStackTrace(); reportFailure(resourceUpdate, operationType, e.getMessage()); } catch (IOException e) { log.error("{} operation failed", operationType, e); e.printStackTrace(); reportFailure(resourceUpdate, operationType, e.getMessage()); } } else { // No _GITER_SPEC to deploy, so nothing was written to Git and nothing will ever reconcile. // Saying so is the difference between an order that fails and an order that hangs. reportFailure(resourceUpdate, operationType, "no _GITER_SPEC characteristic found on the resource"); } noteItem.author("GITER"); Loading @@ -162,6 +169,33 @@ public class ResourceRepoService { return res; } /** * Reports a CREATE/UPDATE that did not reach Git, so the order does not hang on it. * * <p>These paths used to return with no state written at all. That reads as * {@code ResourceHealth.PENDING}, which OSOM treats as "still coming up" and neither promotes nor * demotes on - so a resource whose CR never got deployed pins its service at RESERVED forever, * with no exception and nothing in any log to say why. DOWN is the honest verdict: administratively * in service ({@code UNLOCKED}) but not working ({@code DISABLE}). * * <p>{@code ALARM} is <i>declared</i>, not set. Unlike the status watcher, this update was built by * OSOM from the live catalog entry, so an orchestrator's {@code RESERVED} is already on the object * and {@code declareResourceStatus} has something to guard - no read-back is needed here. A * failure to deploy is still no reason to release someone else's reservation; health carries the * bad news regardless. */ private void reportFailure(ResourceUpdate resourceUpdate, OperationType operationType, String reason) { String message = String.format("%s failed: %s", operationType, reason); resourceUpdate.declareResourceStatus(ResourceStatusType.ALARM); ResourceHealthSupport.applyHealth(resourceUpdate, ResourceHealth.DOWN, ResourceUsageStateType.IDLE); resourceUpdate.addResourceCharacteristicItemShort("status.infoMessage", message, EValueType.TEXT.getValue()); resourceUpdate.addResourceCharacteristicItemShort("status.Health", ResourceHealth.DOWN.name(), EValueType.TEXT.getValue()); } /** * Extract Custom Resource from _GITER_SPEC characteristic */ Loading Loading @@ -351,10 +385,20 @@ public class ResourceRepoService { log.warn("No CR file path found for resource: {}", resourceid); } //set, not declare: teardown is the one path that legitimately ends an allocation. Everywhere //else this controller only observes and must leave a RESERVED/STANDBY hold to whoever made it //(see StatusWatcherService.seedAllocation), but the resource this one held is gone. resourceUpdate.setResourceStatus(ResourceStatusType.UNKNOWN); //GONE is the terminal X.731 verdict: DISABLE + SHUTDOWN. Without it the deleted resource keeps //whatever health it last reported, so deriveHealth still says UP and the supporting service //never reaches TERMINATED - the CR is archived out of Git and the service stays active on a //resource that no longer exists. Clearing resourceStatus alone does not do this: OSOM reads the //X.731 pair and ignores resourceStatus entirely. ResourceHealthSupport.applyHealth(resourceUpdate, ResourceHealth.GONE, ResourceUsageStateType.IDLE); resourceUpdate.addResourceCharacteristicItemShort("status.infoMessage", "Resource deleted", EValueType.TEXT.getValue()); resourceUpdate.addResourceCharacteristicItemShort("status.Health", "Deleted " + new Date(), EValueType.TEXT.getValue()); resourceUpdate.addResourceCharacteristicItemShort("status.Health", ResourceHealth.GONE.name() + " " + new Date(), EValueType.TEXT.getValue()); noteItem.author("GITER"); noteItem.setText(messages.toString()); Loading