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

support SloSle temaplates

parent 6eac3b00
Loading
Loading
Loading
Loading
+37 −14
Original line number Diff line number Diff line
@@ -94,14 +94,24 @@ For this controller:

### Message Flow

**CREATE:**
1. **OpenSlice OSOM** sends a resource request to the controller's CREATE queue
2. **Apache Camel route** (`PartnerRouteBuilder`) intercepts the message and triggers `ResourceRepoService`
3. **ResourceRepoService** reads the spec name from the resource's `resourceSpecification.name`
4. **Spec-based dispatch:** routes to `applyIETFSloSleTemplateSpec`, `applyIETFSliceServiceSpec`, or `applyIETFNetworkSliceServicesSpec` depending on the spec name
5. **RFC 9543 deserializer** parses the JSON characteristic payload using `Rfc9543SliceServiceDeserializer` (custom Jackson `StdDeserializer`)
6. **RESTCONF client** (`RestconfClientImpl`) provisions the slice service to the Network Slice Service Provider using the RFC 9543 wire format
4. **Spec-based dispatch:** routes to `applySloSleTemplateRequest`, `applySliceServiceRequest`, or `applyNetworkSliceServicesRequest` depending on the spec name
5. **RFC 9543 deserializer** parses the JSON characteristic payload (`Rfc9543SloSleTemplateDeserializer` or `Rfc9543SliceServiceDeserializer`)
6. **RESTCONF client** (`RestconfClientImpl`) provisions the resource to the Network Slice Service Provider
7. **Response** is sent back via catalog update (resource status set to `AVAILABLE`, `TERMINATED`, or `UNKNOWN`)

**DELETE:**
1. **OpenSlice OSOM** sends a resource request to the controller's DELETE queue
2. **Apache Camel route** triggers `ResourceRepoService.deleteResource()`
3. **Spec detection from characteristics:** since `ResourceUpdate` carries no spec name, the handler inspects which characteristics are present in the resource:
   - `SloSleTemplateAsJson` present → parses template ID → `RestconfConsumerService.decommissionSloSleTemplate(id)`
   - `SliceServiceAsJson` present → parses service ID → `RestconfConsumerService.decommissionSliceService(id)`
   - `SloSleTemplatesAsJsonArray` / `SliceServicesAsJsonArray` present → `RestconfConsumerService.decommissionNetworkSliceServices()`
4. **Response:** status set to `TERMINATED` + Healthy on success, `UNKNOWN` + Unhealthy on RESTCONF failure, `TERMINATED` + Degraded when no RESTCONF consumer is configured

### Message Headers

Important metadata is included in message headers:
@@ -165,6 +175,14 @@ All RESTCONF payloads sent to the Network Slice Service Provider use the RFC 954

This wrapper is built in `RestconfClientImpl.buildRfc9543Payload(NetworkSliceServices)` using Jackson `ObjectNode`/`ArrayNode` directly, independent of domain model serialization.

### SloSleTemplate Wire Format

`SloSleTemplate` objects are serialized/deserialized with a matched pair of custom Jackson handlers annotated directly on the class:

- **`Rfc9543SloSleTemplateSerializer`** (`StdSerializer<SloSleTemplate>`) — writes only the known RFC 9543 top-level fields (`id`, `description`, `slo-policy`, `sle-policy`, `template-ref`), preventing interface default methods from `LogicalResourceSpecMappable` / `RelatedManagedResourceReference` from leaking into the output. Nested `SloPolicy` and `SlePolicy` are delegated to Jackson's default serialization, which honours their `@JsonProperty` annotations.

- **`Rfc9543SloSleTemplateDeserializer`** (`StdDeserializer<SloSleTemplate>`) — reads `"slo-policy"` and `"sle-policy"` keys explicitly and delegates nested object parsing to Jackson via `codec.treeToValue()`, so `SloPolicy` gets its `"metric-bound"` mapping, `SlePolicy` gets its `"max-occupancy-level"` and `"isolation-requirement"` mappings, etc.

### SliceService Wire Format

`SliceService` objects are serialized/deserialized with a matched pair of custom Jackson handlers:
@@ -202,6 +220,7 @@ All RFC 9543 hyphenated JSON fields are mapped to Java camelCase using `@JsonPro
| `value-description` | `valueDescription` | `MetricBound` |
| `percentile-value` | `percentileValue` | `MetricBound` |
| `max-occupancy-level` | `maxOccupancyLevel` | `SlePolicy` |
| `isolation-requirement` | `isolation` | `SlePolicy` |
| `path-constraints` | `pathConstraints` | `SlePolicy` |
| `admin-status` | `adminStatus` | `ServiceStatus` |
| `oper-status` | `operStatus` | `ServiceStatus` |
@@ -258,15 +277,17 @@ src/main/java/org/etsi/osl/controllers/ietf/ns/
│   └── domain/model/
│       ├── NetworkSliceServices.java               (Top-level RFC 9543 container)
│       ├── SliceService.java                       (@JsonSerialize/@JsonDeserialize pair)
│       ├── SloSleTemplate.java                     (Implements LogicalResourceMappable)
│       ├── SloSleTemplate.java                       (@JsonSerialize/@JsonDeserialize pair)
│       ├── SDP.java                                  (Service Demarcation Point)
│       ├── ConnectionGroup.java                      (Connectivity group)
│       ├── ConnectivityConstruct.java                (P2P/P2MP/A2A endpoints)
│       ├── ServiceStatus.java                        (admin-status / oper-status)
│       ├── ServiceTag.java                           (service-tags entry)
│       ├── ConnectivityType.java                     (Enum: P2P, P2MP, A2A)
│       ├── Rfc9543SliceServiceSerializer.java      (StdSerializer for RFC 9543 wire format)
│       ├── Rfc9543SliceServiceDeserializer.java    (StdDeserializer for RFC 9543 wire format)
│       ├── Rfc9543SloSleTemplateSerializer.java      (StdSerializer for SloSleTemplate)
│       ├── Rfc9543SloSleTemplateDeserializer.java    (StdDeserializer for SloSleTemplate)
│       ├── Rfc9543SliceServiceSerializer.java        (StdSerializer for SliceService)
│       ├── Rfc9543SliceServiceDeserializer.java      (StdDeserializer for SliceService)
│       └── slo_sle/
│           ├── SloPolicy.java                      (Service Level Objectives)
│           ├── SlePolicy.java                      (Service Level Expectations)
@@ -311,8 +332,10 @@ When OpenSlice OSOM creates a resource using a registered resource specification
3. Handler parses the JSON characteristic payload and deserializes to domain objects
4. Controller calls `RestconfClientImpl` to provision the slice via RESTCONF to TerflowSDN
5. Controller updates the resource status in OpenSlice:
   - **Success:** Status = `AVAILABLE`, administrative state = `UNLOCKED`
   - **Failure:** Status = `UNKNOWN`, health = `Unhealthy`, message describes error
   - **CREATE success:** Status = `AVAILABLE`, administrative state = `UNLOCKED`
   - **CREATE failure:** Status = `UNKNOWN`, health = `Unhealthy`, message describes error
   - **DELETE success:** Status = `TERMINATED`, health = `Healthy`
   - **DELETE failure:** Status = `UNKNOWN`, health = `Unhealthy`, message describes error

**Resource creation sequence diagram:** [`doc/resource_creation_lifecycle.puml`](doc/resource_creation_lifecycle.puml)

+1 −1
Original line number Diff line number Diff line
@@ -361,7 +361,7 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner {
                        LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(template);

                        //Override Name, to show NSC templates as specnames...
                        spec.setName(template.getEntityName() );
                        spec.setName(  ResourceSpecificationTemplateRegistry.SPEC_SLO_SLE_TEMPLATE +"_" + template.getEntityName() );

                        spec.setCategory(categoryConfig.getCategoryForSpecifications());
                        spec.setVersion(categoryConfig.getVersion());
+64 −0
Original line number Diff line number Diff line
package org.etsi.osl.controllers.ietf.ns.api.domain.model;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import org.etsi.osl.controllers.ietf.ns.api.domain.model.slo_sle.SlePolicy;
import org.etsi.osl.controllers.ietf.ns.api.domain.model.slo_sle.SliceTemplateRef;
import org.etsi.osl.controllers.ietf.ns.api.domain.model.slo_sle.SloPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Custom Jackson deserializer for RFC 9543 formatted SloSleTemplate JSON.
 *
 * Maps RFC 9543 hyphenated top-level field names:
 * - "slo-policy"   → sloPolicy
 * - "sle-policy"   → slePolicy
 * - "template-ref" → templateRef
 *
 * Nested SloPolicy and SlePolicy parsing is delegated back to Jackson,
 * which honours the @JsonProperty annotations on those classes
 * (e.g. "metric-bound" → metricBounds, "max-occupancy-level" → maxOccupancyLevel,
 *       "isolation-requirement" → isolation).
 */
public class Rfc9543SloSleTemplateDeserializer extends StdDeserializer<SloSleTemplate> {

    private static final Logger logger = LoggerFactory.getLogger(Rfc9543SloSleTemplateDeserializer.class);

    public Rfc9543SloSleTemplateDeserializer() {
        super(SloSleTemplate.class);
    }

    @Override
    public SloSleTemplate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        JsonNode node = jp.getCodec().readTree(jp);
        ObjectMapper codec = (ObjectMapper) jp.getCodec();
        return parseTemplate(node, codec);
    }

    private SloSleTemplate parseTemplate(JsonNode node, ObjectMapper codec) throws IOException {
        SloSleTemplate t = new SloSleTemplate();

        if (node.has("id"))          t.setId(node.get("id").asText());
        if (node.has("description")) t.setDescription(node.get("description").asText());

        if (node.has("slo-policy")) {
            t.setSloPolicy(codec.treeToValue(node.get("slo-policy"), SloPolicy.class));
        }

        if (node.has("sle-policy")) {
            t.setSlePolicy(codec.treeToValue(node.get("sle-policy"), SlePolicy.class));
        }

        if (node.has("template-ref")) {
            t.setTemplateRef(codec.treeToValue(node.get("template-ref"), SliceTemplateRef.class));
        }

        logger.debug("Parsed RFC 9543 SloSleTemplate: {}", t.getId());
        return t;
    }
}
+59 −0
Original line number Diff line number Diff line
package org.etsi.osl.controllers.ietf.ns.api.domain.model;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Custom Jackson serializer for RFC 9543 formatted SloSleTemplate JSON.
 *
 * Writes only the known RFC 9543 wire fields (id, description, slo-policy,
 * sle-policy, template-ref), preventing interface default methods from
 * LogicalResourceSpecMappable / RelatedManagedResourceReference from
 * appearing as unwanted JSON properties.
 *
 * Nested SloPolicy and SlePolicy objects are delegated to Jackson's default
 * serialization, which honours the @JsonProperty annotations on those classes.
 */
public class Rfc9543SloSleTemplateSerializer extends StdSerializer<SloSleTemplate> {

    private static final Logger logger = LoggerFactory.getLogger(Rfc9543SloSleTemplateSerializer.class);

    public Rfc9543SloSleTemplateSerializer() {
        super(SloSleTemplate.class);
    }

    @Override
    public void serialize(SloSleTemplate t, JsonGenerator gen, SerializerProvider provider)
            throws IOException {
        gen.writeStartObject();

        writeIfNotNull(gen, "id",          t.getId());
        writeIfNotNull(gen, "description", t.getDescription());

        if (t.getSloPolicy() != null) {
            gen.writeFieldName("slo-policy");
            provider.defaultSerializeValue(t.getSloPolicy(), gen);
        }

        if (t.getSlePolicy() != null) {
            gen.writeFieldName("sle-policy");
            provider.defaultSerializeValue(t.getSlePolicy(), gen);
        }

        if (t.getTemplateRef() != null) {
            gen.writeFieldName("template-ref");
            provider.defaultSerializeValue(t.getTemplateRef(), gen);
        }

        gen.writeEndObject();
        logger.debug("Serialized RFC 9543 SloSleTemplate: {}", t.getId());
    }

    private void writeIfNotNull(JsonGenerator gen, String field, String value) throws IOException {
        if (value != null) gen.writeStringField(field, value);
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@ import org.etsi.osl.controllers.ietf.ns.domain.common.RelatedManagedResourceRefe
import org.etsi.osl.tmf.ri639.model.LogicalResource;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.persistence.Transient;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -41,6 +43,8 @@ import lombok.extern.slf4j.Slf4j;
 * Based on draft-ietf-teas-ietf-network-slice-nbi-yang-25
 * /network-slice-services/slo-sle-templates/slo-sle-template
 */
@JsonDeserialize(using = Rfc9543SloSleTemplateDeserializer.class)
@JsonSerialize(using = Rfc9543SloSleTemplateSerializer.class)
@Data
@NoArgsConstructor
@AllArgsConstructor
Loading