Commit f33ea3d1 authored by Christos Tranoris's avatar Christos Tranoris
Browse files

fix for #2

parent 39507625
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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>
+32 −0
Original line number Diff line number Diff line
@@ -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  );
+55 −5
Original line number Diff line number Diff line
@@ -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<>();

@@ -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 {
@@ -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;
    }
}
+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();
  }
}
+47 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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");
@@ -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
   */
@@ -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