Loading src/main/java/org/etsi/osl/controllers/tfs/api/SloSleTemplateBootstrapService.java +83 −48 Original line number Original line Diff line number Diff line Loading @@ -7,11 +7,14 @@ import java.io.InputStream; import java.math.BigDecimal; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Set; import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource; import org.etsi.osl.controllers.tfs.api.domain.model.NetworkSliceServices; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceStatus; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceStatus; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceTag; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceTag; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; Loading Loading @@ -396,7 +399,7 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { * @return SliceService domain object * @return SliceService domain object * @throws Exception if JSON parsing fails * @throws Exception if JSON parsing fails */ */ public SliceService parseAndConvertSliceService(String sliceServiceJson) throws Exception { public NetworkSliceServices parseAndConvertSliceService(String sliceServiceJson) throws Exception { log.info("Parsing RFC 9543 SliceService JSON"); log.info("Parsing RFC 9543 SliceService JSON"); try { try { Loading @@ -414,8 +417,11 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { throw new IllegalArgumentException("No slice-service array found"); throw new IllegalArgumentException("No slice-service array found"); } } // Get the first slice service from the array // Parse all slice services from the array JsonNode sliceServiceNode = sliceServicesNode.get(0); List<SliceService> sliceServices = new ArrayList<>(); for (int i = 0; i < sliceServicesNode.size(); i++) { JsonNode sliceServiceNode = sliceServicesNode.get(i); // Parse service identity // Parse service identity String serviceId = sliceServiceNode.get("id").asText(); String serviceId = sliceServiceNode.get("id").asText(); Loading @@ -429,8 +435,6 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { service.setId(serviceId); service.setId(serviceId); service.setDescription(serviceDescription); service.setDescription(serviceDescription); service.setTestOnly(false); service.setTestOnly(false); // Store just the slice service JSON (not the wrapped namespace) service.setJsonRequest(mapper.writeValueAsString(sliceServiceNode)); // Parse SLO/SLE template reference // Parse SLO/SLE template reference if (sliceServiceNode.has("slo-sle-policy")) { if (sliceServiceNode.has("slo-sle-policy")) { Loading Loading @@ -484,8 +488,23 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { } } } } // Add this service to the list sliceServices.add(service); log.info("Successfully parsed RFC 9543 SliceService: {}", serviceId); log.info("Successfully parsed RFC 9543 SliceService: {}", serviceId); return service; } // Create NetworkSliceServices wrapper with all parsed services NetworkSliceServices networkSliceServices = new NetworkSliceServices(); networkSliceServices.setSliceServices(sliceServices); // Build and store the RFC 9543 formatted JSON request (slice-service array) Map<String, List<SliceService>> sliceServiceWrapper = new HashMap<>(); sliceServiceWrapper.put("slice-service", sliceServices); String jsonRequest = mapper.writeValueAsString(sliceServiceWrapper); networkSliceServices.setJsonRequest(jsonRequest); log.info("Successfully created NetworkSliceServices with {} service(s)", sliceServices.size()); return networkSliceServices; } catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) { log.error("Invalid RFC 9543 SliceService JSON format: {}", e.getMessage()); log.error("Invalid RFC 9543 SliceService JSON format: {}", e.getMessage()); Loading Loading @@ -554,18 +573,23 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { } } } } // Parse JSON to SliceService // Parse JSON to NetworkSliceServices SliceService sliceService = parseAndConvertSliceService(serviceJson); NetworkSliceServices networkSliceServices = parseAndConvertSliceService(serviceJson); log.info("Successfully parsed backhaul slice service: {}", sliceService.getId()); log.info("Successfully parsed backhaul network slice services with {} service(s)", networkSliceServices.getSliceServices().size()); // Convert SliceService to LogicalResourceSpecification // Convert NetworkSliceServices to LogicalResourceSpecification LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(sliceService); LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(networkSliceServices); // Enhance specification with backhaul metadata // Enhance specification with backhaul metadata spec.setName( spec.getName() + "_LocalTemplate_" + sliceService.getSloSleTemplate().getId() ); String templateId = networkSliceServices.getSliceServices().isEmpty() ? "unknown" : (networkSliceServices.getSliceServices().get(0).getSloSleTemplate() != null ? networkSliceServices.getSliceServices().get(0).getSloSleTemplate().getId() : "unknown"); spec.setName( spec.getName() + "_LocalTemplate_" + templateId ); spec.setCategory(categoryConfig.getCategoryForSpecifications()); spec.setCategory(categoryConfig.getCategoryForSpecifications()); spec.setVersion(categoryConfig.getVersion()); spec.setVersion(categoryConfig.getVersion()); spec.setDescription(description + " - " + sliceService.getDescription() + spec.setDescription(description + " - " + networkSliceServices.getEntityDescription() + ". Maps 3GPP network slice to IETF RFC 9543 standard format."); ". Maps 3GPP network slice to IETF RFC 9543 standard format."); spec.setType("LogicalResourceSpecification"); spec.setType("LogicalResourceSpecification"); spec.setBaseType("ResourceSpecification"); spec.setBaseType("ResourceSpecification"); Loading Loading @@ -607,11 +631,10 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { for (SloSleTemplate template : providerTemplates) { for (SloSleTemplate template : providerTemplates) { try { try { SliceService exampleService = createExampleSliceServiceForTemplate(template); NetworkSliceServices networkSliceServices = createExampleSliceServiceForTemplate(template); if (exampleService != null) { if (networkSliceServices != null) { // Convert to LogicalResource // Convert NetworkSliceServices to LogicalResourceSpecification //LogicalResource resource = logicalResourceMapper.toLogicalResource(exampleService); LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(networkSliceServices); LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(exampleService); spec.setName( spec.getName() + "_fromTemplate_" + template.getEntityName()); spec.setName( spec.getName() + "_fromTemplate_" + template.getEntityName()); if (spec != null) { if (spec != null) { Loading Loading @@ -642,9 +665,9 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { * - One connection group for point-to-point connectivity * - One connection group for point-to-point connectivity * * * @param template The SloSleTemplate to create an example service for * @param template The SloSleTemplate to create an example service for * @return SliceService instance configured with the template * @return NetworkSliceServices instance configured with the template */ */ private SliceService createExampleSliceServiceForTemplate(SloSleTemplate template) { private NetworkSliceServices createExampleSliceServiceForTemplate(SloSleTemplate template) { try { try { // Create unique service ID // Create unique service ID String serviceId = "slice-service-" + UUID.randomUUID().toString(); String serviceId = "slice-service-" + UUID.randomUUID().toString(); Loading @@ -655,10 +678,6 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { "' from RESTCONF provider"); "' from RESTCONF provider"); service.setTestOnly(false); service.setTestOnly(false); // Generate synthetic example slice service JSON String exampleJson = buildExampleSliceServiceJson(serviceId, template.getId()); service.setJsonRequest(exampleJson); // Set template reference // Set template reference service.setSloSleTemplate(template); service.setSloSleTemplate(template); Loading @@ -673,11 +692,21 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { tag.setValue("provider-template:" + template.getId()); tag.setValue("provider-template:" + template.getId()); service.getServiceTags().add(tag); service.getServiceTags().add(tag); log.debug("Created example SliceService {} for template {}", serviceId, template.getId()); // Wrap in NetworkSliceServices return service; NetworkSliceServices networkSliceServices = new NetworkSliceServices(); List<SliceService> services = new ArrayList<>(); services.add(service); networkSliceServices.setSliceServices(services); // Generate RFC 9543 formatted JSON with slice-service array String exampleJson = buildExampleSliceServiceJson(serviceId, template.getId()); networkSliceServices.setJsonRequest(exampleJson); log.debug("Created example NetworkSliceServices with service {} for template {}", serviceId, template.getId()); return networkSliceServices; } catch (Exception e) { } catch (Exception e) { log.error("Error creating example SliceService for template: {}", template.getId(), e); log.error("Error creating example NetworkSliceServices for template: {}", template.getId(), e); return null; return null; } } } } Loading Loading @@ -812,7 +841,13 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { connectionGroups.set("connection-group", connGroupArray); connectionGroups.set("connection-group", connGroupArray); sliceService.set("connection-groups", connectionGroups); sliceService.set("connection-groups", connectionGroups); return mapper.writeValueAsString(sliceService); // Wrap in RFC 9543 format with slice-service array com.fasterxml.jackson.databind.node.ObjectNode wrapper = mapper.createObjectNode(); com.fasterxml.jackson.databind.node.ArrayNode sliceServiceArray = mapper.createArrayNode(); sliceServiceArray.add(sliceService); wrapper.set("slice-service", sliceServiceArray); return mapper.writeValueAsString(wrapper); } catch (Exception e) { } catch (Exception e) { log.error("Error building example slice service JSON for template: {}", templateId, e); log.error("Error building example slice service JSON for template: {}", templateId, e); Loading src/main/java/org/etsi/osl/controllers/tfs/api/domain/model/NetworkSliceServices.java +94 −1 Original line number Original line Diff line number Diff line Loading @@ -5,20 +5,113 @@ import java.util.List; import lombok.AllArgsConstructor; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NoArgsConstructor; import org.etsi.osl.controllers.tfs.domain.common.LogicalResourceMappable; import org.etsi.osl.tmf.ri639.model.LogicalResource; import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType; import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType; import org.etsi.osl.tmf.ri639.model.ResourceStatusType; /** /** * Root container for IETF Network Slice Services. * Root container for IETF Network Slice Services. * Represents the top-level container for managing network slice services and their * Represents the top-level container for managing network slice services and their * associated SLO/SLE templates as per draft-ietf-teas-ietf-network-slice-nbi-yang-25. * associated SLO/SLE templates as per draft-ietf-teas-ietf-network-slice-nbi-yang-25. * * Implements LogicalResourceMappable to enable conversion to TMF LogicalResource instances. * The jsonRequest field contains the RFC 9543 formatted JSON with slice-service array. */ */ @Data @Data @NoArgsConstructor @NoArgsConstructor @AllArgsConstructor @AllArgsConstructor public class NetworkSliceServices { public class NetworkSliceServices implements LogicalResourceMappable { /** * RFC 9543 JSON request containing the slice-service array. * Format: { "slice-service": [ {...}, {...} ] } */ private String jsonRequest; // 1-to-many relationship: contains multiple SLO/SLE templates // 1-to-many relationship: contains multiple SLO/SLE templates private List<SloSleTemplate> sloSleTemplates = new ArrayList<>(); private List<SloSleTemplate> sloSleTemplates = new ArrayList<>(); // 1-to-many relationship: contains multiple slice services // 1-to-many relationship: contains multiple slice services private List<SliceService> sliceServices = new ArrayList<>(); private List<SliceService> sliceServices = new ArrayList<>(); // ========== LogicalResourceMappable Implementation ========== /** * Get the unique identifier - uses the first slice service ID if available. */ @Override public String getEntityId() { if (sliceServices != null && !sliceServices.isEmpty()) { return sliceServices.get(0).getId(); } return "unknown"; } /** * Get the display name - uses the first slice service ID if available. */ @Override public String getEntityName() { if (sliceServices != null && !sliceServices.isEmpty()) { return sliceServices.get(0).getId(); } return "Network Slice Services"; } /** * Get the description - uses the first slice service description if available. */ @Override public String getEntityDescription() { if (sliceServices != null && !sliceServices.isEmpty() && sliceServices.get(0).getDescription() != null) { return sliceServices.get(0).getDescription(); } return "IETF RFC 9543 Network Slice Services"; } /** * Check if this entity has status mapping. */ @Override public boolean hasStatusMapping() { return sliceServices != null && !sliceServices.isEmpty() && sliceServices.get(0).getStatus() != null; } /** * Map service status to TMF639 resource states - uses the first slice service status. */ @Override public void mapStatusToResourceStates(LogicalResource resource) { if (sliceServices != null && !sliceServices.isEmpty()) { SliceService firstService = sliceServices.get(0); if (firstService.getStatus() != null) { ServiceStatus status = firstService.getStatus(); // Map administrative state String adminState = status.getAdminState(); if (adminState != null && adminState.equals("admin-up")) { resource.setAdministrativeState(ResourceAdministrativeStateType.UNLOCKED); } else if (adminState != null && adminState.equals("admin-down")) { resource.setAdministrativeState(ResourceAdministrativeStateType.LOCKED); } // Map operational state String operState = status.getOperState(); if (operState != null && operState.equals("operational")) { resource.setOperationalState(ResourceOperationalStateType.ENABLE); } else if (operState != null && operState.equals("non-operational")) { resource.setOperationalState(ResourceOperationalStateType.DISABLE); } // Set resource status (active if operational, disabled otherwise) if ("operational".equals(operState)) { resource.setResourceStatus(ResourceStatusType.AVAILABLE); } else { resource.setResourceStatus(ResourceStatusType.SUSPENDED); } } } } } } src/main/java/org/etsi/osl/controllers/tfs/api/domain/model/SliceService.java +1 −79 Original line number Original line Diff line number Diff line Loading @@ -6,19 +6,12 @@ import java.util.List; import lombok.AllArgsConstructor; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NoArgsConstructor; import org.etsi.osl.controllers.tfs.domain.common.LogicalResourceMappable; import org.etsi.osl.tmf.ri639.model.LogicalResource; import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType; import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType; import org.etsi.osl.tmf.ri639.model.ResourceStatusType; /** /** * Represents a customer-facing Network Slice Service (NSS). * Represents a customer-facing Network Slice Service (NSS). * Includes service configuration, status, and all connectivity and resource parameters. * Includes service configuration, status, and all connectivity and resource parameters. * Supports optional test-only feasibility mode and service-level SLO/SLE template reference. * Supports optional test-only feasibility mode and service-level SLO/SLE template reference. * * * Implements LogicalResourceMappable to enable conversion to TMF LogicalResource instances. * * Uses custom RFC 9543 deserializer to handle hyphenated field names in JSON: * Uses custom RFC 9543 deserializer to handle hyphenated field names in JSON: * - "service-tags" → serviceTags * - "service-tags" → serviceTags * - "slo-sle-policy" → sloSleTemplate * - "slo-sle-policy" → sloSleTemplate Loading @@ -29,17 +22,12 @@ import org.etsi.osl.tmf.ri639.model.ResourceStatusType; @Data @Data @NoArgsConstructor @NoArgsConstructor @AllArgsConstructor @AllArgsConstructor public class SliceService implements LogicalResourceMappable { public class SliceService { private String id; private String id; private String description; private String description; private Boolean testOnly = false; private Boolean testOnly = false; private ServiceStatus status; private ServiceStatus status; /** * thia is for now the Slice Service request that will go down to the consumer as is */ private String jsonRequest; // 0-to-1 relationship: references an SLO/SLE template at the service level // 0-to-1 relationship: references an SLO/SLE template at the service level private SloSleTemplate sloSleTemplate; private SloSleTemplate sloSleTemplate; Loading @@ -51,70 +39,4 @@ public class SliceService implements LogicalResourceMappable { // 1-to-many relationship: contains multiple connection groups // 1-to-many relationship: contains multiple connection groups private List<ConnectionGroup> connectionGroups = new ArrayList<>(); private List<ConnectionGroup> connectionGroups = new ArrayList<>(); // ========== LogicalResourceMappable Implementation ========== /** * Get the unique identifier for this slice service. */ @Override public String getEntityId() { return this.id; } /** * Get the display name for this slice service. */ @Override public String getEntityName() { return this.id; } /** * Get the description for this slice service. */ @Override public String getEntityDescription() { return this.description; } /** * Check if this entity has status mapping. * SliceService has a status field, so this should return true. */ @Override public boolean hasStatusMapping() { return this.status != null; } /** * Map service status to TMF639 resource states. */ @Override public void mapStatusToResourceStates(LogicalResource resource) { if (this.status != null) { // Map administrative state String adminState = this.status.getAdminState(); if (adminState != null && adminState.equals("admin-up")) { resource.setAdministrativeState( ResourceAdministrativeStateType.UNLOCKED ); } else if (adminState != null && adminState.equals("admin-down")) { resource.setAdministrativeState( ResourceAdministrativeStateType.UNLOCKED); } // Map operational state String operState = this.status.getOperState(); if (operState != null && operState.equals("operational")) { resource.setOperationalState( ResourceOperationalStateType.ENABLE ); } else if (operState != null && operState.equals("non-operational")) { resource.setOperationalState( ResourceOperationalStateType.DISABLE ); } // Set resource status (active if operational, disabled otherwise) if ("operational".equals(operState)) { resource.setResourceStatus( ResourceStatusType.AVAILABLE ); } else { resource.setResourceStatus(ResourceStatusType.SUSPENDED ); } } } } } src/main/java/org/etsi/osl/controllers/tfs/api/restconf/RestconfClient.java +19 −7 Original line number Original line Diff line number Diff line Loading @@ -22,15 +22,27 @@ import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; public interface RestconfClient { public interface RestconfClient { /** /** * Create a new network slice service on the provider. * Create new network slice services on the provider. * * * HTTP Operation: PUT /restconf/data/ietf-network-slice-service:network-slice-services/slice-service=<id> * HTTP Operation: POST /restconf/data/ietf-network-slice-service:network-slice-services * * * @param service The network slice service to create * The request body follows RFC 9543 format with slice-service as an array: * @return The created slice service with updated status * { * "slice-service": [ * { * "id": "slice-001", * "slo-sle-policy": { "slo-sle-template": "Gold" }, * "sdps": { ... }, * "connection-groups": { ... } * } * ] * } * * @param services List of network slice services to create * @return List of created slice services with updated status * @throws RestconfException if the request fails * @throws RestconfException if the request fails */ */ SliceService createSliceService(SliceService service) throws RestconfException; List<SliceService> createSliceService(List<SliceService> services) throws RestconfException; /** /** * Retrieve a specific network slice service from the provider. * Retrieve a specific network slice service from the provider. Loading src/main/java/org/etsi/osl/controllers/tfs/api/restconf/RestconfClientImpl.java +23 −13 Original line number Original line Diff line number Diff line Loading @@ -3,7 +3,9 @@ package org.etsi.osl.controllers.tfs.api.restconf; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.Base64; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.List; import java.util.Map; import org.etsi.osl.controllers.tfs.api.domain.model.NetworkSliceServices; import org.etsi.osl.controllers.tfs.api.domain.model.NetworkSliceServices; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; import org.slf4j.Logger; import org.slf4j.Logger; Loading Loading @@ -71,39 +73,47 @@ public class RestconfClientImpl implements RestconfClient { private ObjectMapper objectMapper; private ObjectMapper objectMapper; @Override @Override public SliceService createSliceService(SliceService service) throws RestconfException { public List<SliceService> createSliceService(List<SliceService> services) throws RestconfException { if (service == null || service.getId() == null) { if (services == null || services.isEmpty()) { throw new RestconfException("Service and service ID must not be null"); throw new RestconfException("Services list must not be null or empty"); } } String uri = buildServiceUriPost(service.getId()); String uri = providerUrl + "/restconf/data/ietf-network-slice-service:network-slice-services"; logger.info("Creating network slice service: {}", service.getId()); logger.info("Creating {} network slice service(s)", services.size()); try { try { HttpEntity<SliceService> requestEntity = new HttpEntity<>(service, buildHeaders()); // Build RFC 9543 format: { "slice-service": [ {...}, {...} ] } ResponseEntity<SliceService> response = restTemplate.exchange( Map<String, List<SliceService>> requestBody = new HashMap<>(); requestBody.put("slice-service", services); HttpEntity<Map<String, List<SliceService>>> requestEntity = new HttpEntity<>(requestBody, buildHeaders()); // Use a custom response type to handle the RFC 9543 array response ResponseEntity<String> response = restTemplate.exchange( uri, uri, HttpMethod.POST, HttpMethod.POST, requestEntity, requestEntity, SliceService.class String.class ); ); if (response.getStatusCode().is2xxSuccessful()) { if (response.getStatusCode().is2xxSuccessful()) { logger.info("Successfully created network slice service: {}", service.getId()); logger.info("Successfully created {} network slice service(s)", services.size()); return response.getBody(); // Parse the response and return the created services // For now, return the input services (they should be echoed back with status) return services; } else { } else { throw new RestconfException( throw new RestconfException( response.getStatusCode().value(), response.getStatusCode().value(), "protocol", "protocol", "operation-failed", "operation-failed", "Failed to create network slice service" "Failed to create network slice services" ); ); } } } catch (HttpClientErrorException e) { } catch (HttpClientErrorException e) { throw handleHttpError(e); throw handleHttpError(e); } catch (Exception e) { } catch (Exception e) { logger.error("Error creating network slice service: {}", service.getId(), e); logger.error("Error creating network slice services", e); throw new RestconfException("Failed to create network slice service: " + e.getMessage(), e); throw new RestconfException("Failed to create network slice services: " + e.getMessage(), e); } } } } Loading Loading
src/main/java/org/etsi/osl/controllers/tfs/api/SloSleTemplateBootstrapService.java +83 −48 Original line number Original line Diff line number Diff line Loading @@ -7,11 +7,14 @@ import java.io.InputStream; import java.math.BigDecimal; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Set; import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource; import org.etsi.osl.controllers.tfs.api.domain.model.NetworkSliceServices; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceStatus; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceStatus; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceTag; import org.etsi.osl.controllers.tfs.api.domain.model.ServiceTag; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; Loading Loading @@ -396,7 +399,7 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { * @return SliceService domain object * @return SliceService domain object * @throws Exception if JSON parsing fails * @throws Exception if JSON parsing fails */ */ public SliceService parseAndConvertSliceService(String sliceServiceJson) throws Exception { public NetworkSliceServices parseAndConvertSliceService(String sliceServiceJson) throws Exception { log.info("Parsing RFC 9543 SliceService JSON"); log.info("Parsing RFC 9543 SliceService JSON"); try { try { Loading @@ -414,8 +417,11 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { throw new IllegalArgumentException("No slice-service array found"); throw new IllegalArgumentException("No slice-service array found"); } } // Get the first slice service from the array // Parse all slice services from the array JsonNode sliceServiceNode = sliceServicesNode.get(0); List<SliceService> sliceServices = new ArrayList<>(); for (int i = 0; i < sliceServicesNode.size(); i++) { JsonNode sliceServiceNode = sliceServicesNode.get(i); // Parse service identity // Parse service identity String serviceId = sliceServiceNode.get("id").asText(); String serviceId = sliceServiceNode.get("id").asText(); Loading @@ -429,8 +435,6 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { service.setId(serviceId); service.setId(serviceId); service.setDescription(serviceDescription); service.setDescription(serviceDescription); service.setTestOnly(false); service.setTestOnly(false); // Store just the slice service JSON (not the wrapped namespace) service.setJsonRequest(mapper.writeValueAsString(sliceServiceNode)); // Parse SLO/SLE template reference // Parse SLO/SLE template reference if (sliceServiceNode.has("slo-sle-policy")) { if (sliceServiceNode.has("slo-sle-policy")) { Loading Loading @@ -484,8 +488,23 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { } } } } // Add this service to the list sliceServices.add(service); log.info("Successfully parsed RFC 9543 SliceService: {}", serviceId); log.info("Successfully parsed RFC 9543 SliceService: {}", serviceId); return service; } // Create NetworkSliceServices wrapper with all parsed services NetworkSliceServices networkSliceServices = new NetworkSliceServices(); networkSliceServices.setSliceServices(sliceServices); // Build and store the RFC 9543 formatted JSON request (slice-service array) Map<String, List<SliceService>> sliceServiceWrapper = new HashMap<>(); sliceServiceWrapper.put("slice-service", sliceServices); String jsonRequest = mapper.writeValueAsString(sliceServiceWrapper); networkSliceServices.setJsonRequest(jsonRequest); log.info("Successfully created NetworkSliceServices with {} service(s)", sliceServices.size()); return networkSliceServices; } catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) { log.error("Invalid RFC 9543 SliceService JSON format: {}", e.getMessage()); log.error("Invalid RFC 9543 SliceService JSON format: {}", e.getMessage()); Loading Loading @@ -554,18 +573,23 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { } } } } // Parse JSON to SliceService // Parse JSON to NetworkSliceServices SliceService sliceService = parseAndConvertSliceService(serviceJson); NetworkSliceServices networkSliceServices = parseAndConvertSliceService(serviceJson); log.info("Successfully parsed backhaul slice service: {}", sliceService.getId()); log.info("Successfully parsed backhaul network slice services with {} service(s)", networkSliceServices.getSliceServices().size()); // Convert SliceService to LogicalResourceSpecification // Convert NetworkSliceServices to LogicalResourceSpecification LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(sliceService); LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(networkSliceServices); // Enhance specification with backhaul metadata // Enhance specification with backhaul metadata spec.setName( spec.getName() + "_LocalTemplate_" + sliceService.getSloSleTemplate().getId() ); String templateId = networkSliceServices.getSliceServices().isEmpty() ? "unknown" : (networkSliceServices.getSliceServices().get(0).getSloSleTemplate() != null ? networkSliceServices.getSliceServices().get(0).getSloSleTemplate().getId() : "unknown"); spec.setName( spec.getName() + "_LocalTemplate_" + templateId ); spec.setCategory(categoryConfig.getCategoryForSpecifications()); spec.setCategory(categoryConfig.getCategoryForSpecifications()); spec.setVersion(categoryConfig.getVersion()); spec.setVersion(categoryConfig.getVersion()); spec.setDescription(description + " - " + sliceService.getDescription() + spec.setDescription(description + " - " + networkSliceServices.getEntityDescription() + ". Maps 3GPP network slice to IETF RFC 9543 standard format."); ". Maps 3GPP network slice to IETF RFC 9543 standard format."); spec.setType("LogicalResourceSpecification"); spec.setType("LogicalResourceSpecification"); spec.setBaseType("ResourceSpecification"); spec.setBaseType("ResourceSpecification"); Loading Loading @@ -607,11 +631,10 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { for (SloSleTemplate template : providerTemplates) { for (SloSleTemplate template : providerTemplates) { try { try { SliceService exampleService = createExampleSliceServiceForTemplate(template); NetworkSliceServices networkSliceServices = createExampleSliceServiceForTemplate(template); if (exampleService != null) { if (networkSliceServices != null) { // Convert to LogicalResource // Convert NetworkSliceServices to LogicalResourceSpecification //LogicalResource resource = logicalResourceMapper.toLogicalResource(exampleService); LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(networkSliceServices); LogicalResourceSpecification spec = mapper.toLogicalResourceSpec(exampleService); spec.setName( spec.getName() + "_fromTemplate_" + template.getEntityName()); spec.setName( spec.getName() + "_fromTemplate_" + template.getEntityName()); if (spec != null) { if (spec != null) { Loading Loading @@ -642,9 +665,9 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { * - One connection group for point-to-point connectivity * - One connection group for point-to-point connectivity * * * @param template The SloSleTemplate to create an example service for * @param template The SloSleTemplate to create an example service for * @return SliceService instance configured with the template * @return NetworkSliceServices instance configured with the template */ */ private SliceService createExampleSliceServiceForTemplate(SloSleTemplate template) { private NetworkSliceServices createExampleSliceServiceForTemplate(SloSleTemplate template) { try { try { // Create unique service ID // Create unique service ID String serviceId = "slice-service-" + UUID.randomUUID().toString(); String serviceId = "slice-service-" + UUID.randomUUID().toString(); Loading @@ -655,10 +678,6 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { "' from RESTCONF provider"); "' from RESTCONF provider"); service.setTestOnly(false); service.setTestOnly(false); // Generate synthetic example slice service JSON String exampleJson = buildExampleSliceServiceJson(serviceId, template.getId()); service.setJsonRequest(exampleJson); // Set template reference // Set template reference service.setSloSleTemplate(template); service.setSloSleTemplate(template); Loading @@ -673,11 +692,21 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { tag.setValue("provider-template:" + template.getId()); tag.setValue("provider-template:" + template.getId()); service.getServiceTags().add(tag); service.getServiceTags().add(tag); log.debug("Created example SliceService {} for template {}", serviceId, template.getId()); // Wrap in NetworkSliceServices return service; NetworkSliceServices networkSliceServices = new NetworkSliceServices(); List<SliceService> services = new ArrayList<>(); services.add(service); networkSliceServices.setSliceServices(services); // Generate RFC 9543 formatted JSON with slice-service array String exampleJson = buildExampleSliceServiceJson(serviceId, template.getId()); networkSliceServices.setJsonRequest(exampleJson); log.debug("Created example NetworkSliceServices with service {} for template {}", serviceId, template.getId()); return networkSliceServices; } catch (Exception e) { } catch (Exception e) { log.error("Error creating example SliceService for template: {}", template.getId(), e); log.error("Error creating example NetworkSliceServices for template: {}", template.getId(), e); return null; return null; } } } } Loading Loading @@ -812,7 +841,13 @@ public class SloSleTemplateBootstrapService implements CommandLineRunner { connectionGroups.set("connection-group", connGroupArray); connectionGroups.set("connection-group", connGroupArray); sliceService.set("connection-groups", connectionGroups); sliceService.set("connection-groups", connectionGroups); return mapper.writeValueAsString(sliceService); // Wrap in RFC 9543 format with slice-service array com.fasterxml.jackson.databind.node.ObjectNode wrapper = mapper.createObjectNode(); com.fasterxml.jackson.databind.node.ArrayNode sliceServiceArray = mapper.createArrayNode(); sliceServiceArray.add(sliceService); wrapper.set("slice-service", sliceServiceArray); return mapper.writeValueAsString(wrapper); } catch (Exception e) { } catch (Exception e) { log.error("Error building example slice service JSON for template: {}", templateId, e); log.error("Error building example slice service JSON for template: {}", templateId, e); Loading
src/main/java/org/etsi/osl/controllers/tfs/api/domain/model/NetworkSliceServices.java +94 −1 Original line number Original line Diff line number Diff line Loading @@ -5,20 +5,113 @@ import java.util.List; import lombok.AllArgsConstructor; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NoArgsConstructor; import org.etsi.osl.controllers.tfs.domain.common.LogicalResourceMappable; import org.etsi.osl.tmf.ri639.model.LogicalResource; import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType; import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType; import org.etsi.osl.tmf.ri639.model.ResourceStatusType; /** /** * Root container for IETF Network Slice Services. * Root container for IETF Network Slice Services. * Represents the top-level container for managing network slice services and their * Represents the top-level container for managing network slice services and their * associated SLO/SLE templates as per draft-ietf-teas-ietf-network-slice-nbi-yang-25. * associated SLO/SLE templates as per draft-ietf-teas-ietf-network-slice-nbi-yang-25. * * Implements LogicalResourceMappable to enable conversion to TMF LogicalResource instances. * The jsonRequest field contains the RFC 9543 formatted JSON with slice-service array. */ */ @Data @Data @NoArgsConstructor @NoArgsConstructor @AllArgsConstructor @AllArgsConstructor public class NetworkSliceServices { public class NetworkSliceServices implements LogicalResourceMappable { /** * RFC 9543 JSON request containing the slice-service array. * Format: { "slice-service": [ {...}, {...} ] } */ private String jsonRequest; // 1-to-many relationship: contains multiple SLO/SLE templates // 1-to-many relationship: contains multiple SLO/SLE templates private List<SloSleTemplate> sloSleTemplates = new ArrayList<>(); private List<SloSleTemplate> sloSleTemplates = new ArrayList<>(); // 1-to-many relationship: contains multiple slice services // 1-to-many relationship: contains multiple slice services private List<SliceService> sliceServices = new ArrayList<>(); private List<SliceService> sliceServices = new ArrayList<>(); // ========== LogicalResourceMappable Implementation ========== /** * Get the unique identifier - uses the first slice service ID if available. */ @Override public String getEntityId() { if (sliceServices != null && !sliceServices.isEmpty()) { return sliceServices.get(0).getId(); } return "unknown"; } /** * Get the display name - uses the first slice service ID if available. */ @Override public String getEntityName() { if (sliceServices != null && !sliceServices.isEmpty()) { return sliceServices.get(0).getId(); } return "Network Slice Services"; } /** * Get the description - uses the first slice service description if available. */ @Override public String getEntityDescription() { if (sliceServices != null && !sliceServices.isEmpty() && sliceServices.get(0).getDescription() != null) { return sliceServices.get(0).getDescription(); } return "IETF RFC 9543 Network Slice Services"; } /** * Check if this entity has status mapping. */ @Override public boolean hasStatusMapping() { return sliceServices != null && !sliceServices.isEmpty() && sliceServices.get(0).getStatus() != null; } /** * Map service status to TMF639 resource states - uses the first slice service status. */ @Override public void mapStatusToResourceStates(LogicalResource resource) { if (sliceServices != null && !sliceServices.isEmpty()) { SliceService firstService = sliceServices.get(0); if (firstService.getStatus() != null) { ServiceStatus status = firstService.getStatus(); // Map administrative state String adminState = status.getAdminState(); if (adminState != null && adminState.equals("admin-up")) { resource.setAdministrativeState(ResourceAdministrativeStateType.UNLOCKED); } else if (adminState != null && adminState.equals("admin-down")) { resource.setAdministrativeState(ResourceAdministrativeStateType.LOCKED); } // Map operational state String operState = status.getOperState(); if (operState != null && operState.equals("operational")) { resource.setOperationalState(ResourceOperationalStateType.ENABLE); } else if (operState != null && operState.equals("non-operational")) { resource.setOperationalState(ResourceOperationalStateType.DISABLE); } // Set resource status (active if operational, disabled otherwise) if ("operational".equals(operState)) { resource.setResourceStatus(ResourceStatusType.AVAILABLE); } else { resource.setResourceStatus(ResourceStatusType.SUSPENDED); } } } } } }
src/main/java/org/etsi/osl/controllers/tfs/api/domain/model/SliceService.java +1 −79 Original line number Original line Diff line number Diff line Loading @@ -6,19 +6,12 @@ import java.util.List; import lombok.AllArgsConstructor; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NoArgsConstructor; import org.etsi.osl.controllers.tfs.domain.common.LogicalResourceMappable; import org.etsi.osl.tmf.ri639.model.LogicalResource; import org.etsi.osl.tmf.ri639.model.ResourceAdministrativeStateType; import org.etsi.osl.tmf.ri639.model.ResourceOperationalStateType; import org.etsi.osl.tmf.ri639.model.ResourceStatusType; /** /** * Represents a customer-facing Network Slice Service (NSS). * Represents a customer-facing Network Slice Service (NSS). * Includes service configuration, status, and all connectivity and resource parameters. * Includes service configuration, status, and all connectivity and resource parameters. * Supports optional test-only feasibility mode and service-level SLO/SLE template reference. * Supports optional test-only feasibility mode and service-level SLO/SLE template reference. * * * Implements LogicalResourceMappable to enable conversion to TMF LogicalResource instances. * * Uses custom RFC 9543 deserializer to handle hyphenated field names in JSON: * Uses custom RFC 9543 deserializer to handle hyphenated field names in JSON: * - "service-tags" → serviceTags * - "service-tags" → serviceTags * - "slo-sle-policy" → sloSleTemplate * - "slo-sle-policy" → sloSleTemplate Loading @@ -29,17 +22,12 @@ import org.etsi.osl.tmf.ri639.model.ResourceStatusType; @Data @Data @NoArgsConstructor @NoArgsConstructor @AllArgsConstructor @AllArgsConstructor public class SliceService implements LogicalResourceMappable { public class SliceService { private String id; private String id; private String description; private String description; private Boolean testOnly = false; private Boolean testOnly = false; private ServiceStatus status; private ServiceStatus status; /** * thia is for now the Slice Service request that will go down to the consumer as is */ private String jsonRequest; // 0-to-1 relationship: references an SLO/SLE template at the service level // 0-to-1 relationship: references an SLO/SLE template at the service level private SloSleTemplate sloSleTemplate; private SloSleTemplate sloSleTemplate; Loading @@ -51,70 +39,4 @@ public class SliceService implements LogicalResourceMappable { // 1-to-many relationship: contains multiple connection groups // 1-to-many relationship: contains multiple connection groups private List<ConnectionGroup> connectionGroups = new ArrayList<>(); private List<ConnectionGroup> connectionGroups = new ArrayList<>(); // ========== LogicalResourceMappable Implementation ========== /** * Get the unique identifier for this slice service. */ @Override public String getEntityId() { return this.id; } /** * Get the display name for this slice service. */ @Override public String getEntityName() { return this.id; } /** * Get the description for this slice service. */ @Override public String getEntityDescription() { return this.description; } /** * Check if this entity has status mapping. * SliceService has a status field, so this should return true. */ @Override public boolean hasStatusMapping() { return this.status != null; } /** * Map service status to TMF639 resource states. */ @Override public void mapStatusToResourceStates(LogicalResource resource) { if (this.status != null) { // Map administrative state String adminState = this.status.getAdminState(); if (adminState != null && adminState.equals("admin-up")) { resource.setAdministrativeState( ResourceAdministrativeStateType.UNLOCKED ); } else if (adminState != null && adminState.equals("admin-down")) { resource.setAdministrativeState( ResourceAdministrativeStateType.UNLOCKED); } // Map operational state String operState = this.status.getOperState(); if (operState != null && operState.equals("operational")) { resource.setOperationalState( ResourceOperationalStateType.ENABLE ); } else if (operState != null && operState.equals("non-operational")) { resource.setOperationalState( ResourceOperationalStateType.DISABLE ); } // Set resource status (active if operational, disabled otherwise) if ("operational".equals(operState)) { resource.setResourceStatus( ResourceStatusType.AVAILABLE ); } else { resource.setResourceStatus(ResourceStatusType.SUSPENDED ); } } } } }
src/main/java/org/etsi/osl/controllers/tfs/api/restconf/RestconfClient.java +19 −7 Original line number Original line Diff line number Diff line Loading @@ -22,15 +22,27 @@ import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; public interface RestconfClient { public interface RestconfClient { /** /** * Create a new network slice service on the provider. * Create new network slice services on the provider. * * * HTTP Operation: PUT /restconf/data/ietf-network-slice-service:network-slice-services/slice-service=<id> * HTTP Operation: POST /restconf/data/ietf-network-slice-service:network-slice-services * * * @param service The network slice service to create * The request body follows RFC 9543 format with slice-service as an array: * @return The created slice service with updated status * { * "slice-service": [ * { * "id": "slice-001", * "slo-sle-policy": { "slo-sle-template": "Gold" }, * "sdps": { ... }, * "connection-groups": { ... } * } * ] * } * * @param services List of network slice services to create * @return List of created slice services with updated status * @throws RestconfException if the request fails * @throws RestconfException if the request fails */ */ SliceService createSliceService(SliceService service) throws RestconfException; List<SliceService> createSliceService(List<SliceService> services) throws RestconfException; /** /** * Retrieve a specific network slice service from the provider. * Retrieve a specific network slice service from the provider. Loading
src/main/java/org/etsi/osl/controllers/tfs/api/restconf/RestconfClientImpl.java +23 −13 Original line number Original line Diff line number Diff line Loading @@ -3,7 +3,9 @@ package org.etsi.osl.controllers.tfs.api.restconf; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.Base64; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.List; import java.util.Map; import org.etsi.osl.controllers.tfs.api.domain.model.NetworkSliceServices; import org.etsi.osl.controllers.tfs.api.domain.model.NetworkSliceServices; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; import org.etsi.osl.controllers.tfs.api.domain.model.SliceService; import org.slf4j.Logger; import org.slf4j.Logger; Loading Loading @@ -71,39 +73,47 @@ public class RestconfClientImpl implements RestconfClient { private ObjectMapper objectMapper; private ObjectMapper objectMapper; @Override @Override public SliceService createSliceService(SliceService service) throws RestconfException { public List<SliceService> createSliceService(List<SliceService> services) throws RestconfException { if (service == null || service.getId() == null) { if (services == null || services.isEmpty()) { throw new RestconfException("Service and service ID must not be null"); throw new RestconfException("Services list must not be null or empty"); } } String uri = buildServiceUriPost(service.getId()); String uri = providerUrl + "/restconf/data/ietf-network-slice-service:network-slice-services"; logger.info("Creating network slice service: {}", service.getId()); logger.info("Creating {} network slice service(s)", services.size()); try { try { HttpEntity<SliceService> requestEntity = new HttpEntity<>(service, buildHeaders()); // Build RFC 9543 format: { "slice-service": [ {...}, {...} ] } ResponseEntity<SliceService> response = restTemplate.exchange( Map<String, List<SliceService>> requestBody = new HashMap<>(); requestBody.put("slice-service", services); HttpEntity<Map<String, List<SliceService>>> requestEntity = new HttpEntity<>(requestBody, buildHeaders()); // Use a custom response type to handle the RFC 9543 array response ResponseEntity<String> response = restTemplate.exchange( uri, uri, HttpMethod.POST, HttpMethod.POST, requestEntity, requestEntity, SliceService.class String.class ); ); if (response.getStatusCode().is2xxSuccessful()) { if (response.getStatusCode().is2xxSuccessful()) { logger.info("Successfully created network slice service: {}", service.getId()); logger.info("Successfully created {} network slice service(s)", services.size()); return response.getBody(); // Parse the response and return the created services // For now, return the input services (they should be echoed back with status) return services; } else { } else { throw new RestconfException( throw new RestconfException( response.getStatusCode().value(), response.getStatusCode().value(), "protocol", "protocol", "operation-failed", "operation-failed", "Failed to create network slice service" "Failed to create network slice services" ); ); } } } catch (HttpClientErrorException e) { } catch (HttpClientErrorException e) { throw handleHttpError(e); throw handleHttpError(e); } catch (Exception e) { } catch (Exception e) { logger.error("Error creating network slice service: {}", service.getId(), e); logger.error("Error creating network slice services", e); throw new RestconfException("Failed to create network slice service: " + e.getMessage(), e); throw new RestconfException("Failed to create network slice services: " + e.getMessage(), e); } } } } Loading