Skip to content

Commit c3ec4f4

Browse files
haniotakAlejandro Albinozrecore
authored
1.0.32 release (#587)
* merge master to develop (#583) * hotfix for missing validation (#577) * Updating oscars-backend service to support nullable projectId property * Feedback updates * Updating tests to cover NsiService with new Connection.projectId field * Cleaning up HoldController imports. Updating HoldControllerSteps to include a test with a projectId field when placing a connection hold * Ensuring the NsiService.hold() method only attempts to process the projectId field if it's not null, and not empty * add CORS origin customization property (#581) * fix: [OS-604] yang patch serialization (#582) * fix serialization error * more serialization fixes update spring boot and other libs * add auth interceptor to patch client * remove baseUrl from restClient --------- Co-authored-by: Alejandro Albino <aalbino@lbl.gov> Co-authored-by: Alex Albino <webmaster@alexventure.com> * Adding initial testing for NSI SOAP API (NsiProvider), with and without projectId field * Adding topology service mock data for NsiProvider testing * Adding stubs * make nsiprovider single test pass * Current work sorting out Nsi Provider test steps * Cleaning up NsiProvider cuke testing * Held connections now get cleared per test step in NsiProviderStep * Ensuring a blank projectId field with NSI SOAP API reserve() call results in a null projectId * feat: [OS579] add project id set & view in frontend (#585) * add project id set & view in frontend adjust hold delay minor fix for standalone CORS config * readjust hold delay --------- Co-authored-by: Alejandro Albino <aalbino@lbl.gov> Co-authored-by: Alex Albino <webmaster@alexventure.com>
1 parent b5f5476 commit c3ec4f4

19 files changed

Lines changed: 412 additions & 12 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
> Sep 2025
44
- OS-600 CORS origin customization
55
- OS-604 YANG PATCH serialization hotfix
6+
- OS-579 Project id frontend
67

78
### 1.2.30
89
> July 2025

backend/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,12 @@
337337
<version>4.2.2</version>
338338
<scope>test</scope>
339339
</dependency>
340+
341+
<dependency>
342+
<groupId>org.apache.commons</groupId>
343+
<artifactId>commons-text</artifactId>
344+
<version>1.14.0</version>
345+
</dependency>
340346
</dependencies>
341347
<reporting>
342348
<plugins>

backend/src/main/java/net/es/oscars/nsi/ent/NsiMapping.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class NsiMapping {
3131
@NonNull
3232
private String nsiGri;
3333

34-
// OSCARS connectino ID
34+
// OSCARS connection ID
3535
@NonNull
3636
private String oscarsConnectionId;
3737

backend/src/main/java/net/es/oscars/nsi/svc/NsiMappingService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,10 @@ public Pair<List<Fixture>, List<Junction>> fixturesAndJunctionsFor(P2PServiceBas
376376
Integer zVlanId = vlans.get(z_urn.getPort().getUrn() + "#Z");
377377

378378
if (aVlanId == null) {
379-
throw new NsiValidationException("vlan(s) unavailable for " + src, NsiErrors.UNAVAIL_ERROR);
379+
throw new NsiValidationException("vlan(s) unavailable for src " + src, NsiErrors.UNAVAIL_ERROR);
380380

381381
} else if (zVlanId == null) {
382-
throw new NsiValidationException("vlan(s) unavailable for " + dst, NsiErrors.UNAVAIL_ERROR);
382+
throw new NsiValidationException("vlan(s) unavailable for dst " + dst, NsiErrors.UNAVAIL_ERROR);
383383
}
384384

385385
Fixture aF = Fixture.builder()

backend/src/main/java/net/es/oscars/nsi/svc/NsiService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
@Data
5656
public class NsiService {
5757

58+
private int errorCount = 0;
59+
5860
public NsiQueries nsiQueries;
5961
@Value("${resv.timeout}")
6062
private Integer resvTimeout;
@@ -163,7 +165,7 @@ public boolean reserve(CommonHeaderType header, NsiMapping mapping, ReserveType
163165

164166
} else {
165167

166-
log.error("unable to hold, sending error callback for {}", mapping.getNsiConnectionId());
168+
log.error("unable to hold, sending error callback for {}, message: code {}, {}", mapping.getNsiConnectionId(), holdResult.getErrorCode(), holdResult.getErrorMessage());
167169
errorMessage = holdResult.getErrorMessage();
168170
errorCode = holdResult.getErrorCode();
169171
tvps = holdResult.getTvps();
@@ -955,6 +957,8 @@ public void errCallback(NsiEvent event, String nsaId, String nsiConnectionId, Ns
955957
String error, NsiErrors errNum, List<TypeValuePairType> tvps, String corrId) {
956958
try {
957959

960+
errorCount++;
961+
958962
NsiRequesterNSA requesterNSA = this.nsiHeaderUtils.getRequesterNsa(nsaId);
959963
boolean performCallback = false;
960964
if (requesterNSA.getCallbackUrl().isEmpty()) {

backend/src/main/java/net/es/oscars/resv/svc/validators/ConnServiceScheduleValidate.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public boolean checkBeginTime(SimpleConnection inConn, Errors errors) {
8181
Instant rejectBefore = Instant.now().minus(5, ChronoUnit.MINUTES);
8282
if (begin.isBefore(rejectBefore) && !connectionMode.equals(ConnectionMode.MODIFY)) {
8383
beginValid = false;
84-
errors.rejectValue("begin", null, "begin time is more than 5 minutes in the past");
84+
errors.rejectValue("begin", null, "begin time " + begin.toString() + " is more than 5 minutes in the past");
8585
} else {
8686
// if we are set to start to up to +30 sec from now,
8787
// we (silently) modify the begin timestamp and we
@@ -115,7 +115,7 @@ public boolean checkEndTime(SimpleConnection inConn, Errors errors) {
115115
end = Instant.ofEpochSecond(inConn.getEnd());
116116
if (!end.isAfter(Instant.now())) {
117117
endValid = false;
118-
errors.rejectValue("end", null, "end date is in the past");
118+
errors.rejectValue("end", null, "end date " + end.toString() + " is in the past");
119119
} else if (!end.isAfter(checkedBeginTime)) {
120120
endValid = false;
121121
errors.rejectValue("end", null, "end date not past begin()");

backend/src/main/java/net/es/oscars/web/WebConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void addCorsMappings(@NonNull CorsRegistry registry) {
2626

2727
if (startupProperties.getStandalone()) {
2828
registry.addMapping("/**")
29-
.allowedOrigins("*")
29+
.allowedOriginPatterns("*")
3030
.allowedMethods("*")
3131
.allowedHeaders("*")
3232
.allowCredentials(true);
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
package net.es.oscars.cuke;
2+
3+
import static net.es.oscars.app.util.PrettyPrinter.prettyLog;
4+
5+
import java.io.InputStream;
6+
import java.nio.charset.Charset;
7+
import java.time.Instant;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
11+
import org.apache.commons.text.StringSubstitutor;
12+
import org.junit.experimental.categories.Category;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.boot.test.context.SpringBootTest;
15+
import org.springframework.boot.test.web.client.TestRestTemplate;
16+
import org.springframework.core.io.ClassPathResource;
17+
import org.springframework.http.HttpEntity;
18+
import org.springframework.http.HttpHeaders;
19+
import org.springframework.http.HttpMethod;
20+
import org.springframework.http.ResponseEntity;
21+
import org.springframework.util.StreamUtils;
22+
23+
import org.springframework.http.HttpStatus;
24+
import org.springframework.http.MediaType;
25+
26+
import io.cucumber.java.Before;
27+
import io.cucumber.java.en.Given;
28+
import io.cucumber.java.en.Then;
29+
import io.cucumber.java.en.When;
30+
import lombok.extern.slf4j.Slf4j;
31+
import net.es.oscars.app.Startup;
32+
import net.es.oscars.ctg.UnitTests;
33+
import net.es.oscars.nsi.svc.NsiAsyncQueue;
34+
import net.es.oscars.nsi.svc.NsiConnectionEventService;
35+
import net.es.oscars.nsi.svc.NsiService;
36+
import net.es.oscars.resv.svc.ConnService;
37+
import net.es.oscars.soap.NsiProvider;
38+
import net.es.oscars.topo.TopoService;
39+
import net.es.oscars.topo.beans.Topology;
40+
import net.es.oscars.topo.pop.TopoPopulator;
41+
import net.es.oscars.topo.svc.TopologyStore;
42+
43+
@Slf4j
44+
@Category({UnitTests.class})
45+
@SpringBootTest(
46+
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
47+
classes = {
48+
TopoService.class,
49+
NsiService.class,
50+
NsiAsyncQueue.class,
51+
NsiConnectionEventService.class,
52+
53+
NsiProvider.class,
54+
}
55+
)
56+
public class NsiProviderSteps extends CucumberSteps {
57+
@Autowired
58+
private CucumberWorld world;
59+
60+
@Autowired
61+
private TestRestTemplate restTemplate;
62+
63+
@Autowired
64+
private Startup startup;
65+
66+
@Autowired
67+
private TopologyStore topoService;
68+
69+
@Autowired
70+
private TopoPopulator topoPopulator;
71+
72+
@Autowired
73+
private ConnService connService;
74+
75+
@Autowired
76+
private NsiService nsiService;
77+
78+
@Autowired
79+
private NsiAsyncQueue queue;
80+
81+
private ResponseEntity<String> response;
82+
83+
@Before("@NsiProviderSteps")
84+
public void before() throws Exception {
85+
86+
try {
87+
startup.setInStartup(false);
88+
queue.queue.clear();
89+
topoService.clear();
90+
nsiService.setErrorCount(0);
91+
loadTopology();
92+
releaseAllConnections();
93+
} catch (Exception e) {
94+
world.add(e);
95+
log.error("NsiProviderSteps Error - {}", e);
96+
}
97+
}
98+
99+
@Given("The NSI connection is queued for asynchronous reservation while not including a projectId")
100+
public void the_nsi_connection_is_queued_for_asynchronous_reservation_while_not_including_a_project_id() throws Throwable {
101+
try {
102+
queueNsiConnection();
103+
} catch (Exception e) {
104+
world.add(e);
105+
log.error("NsiProviderSteps Error - {}", e);
106+
}
107+
}
108+
109+
@Given("The NSI connection is queued for asynchronous reservation while including a projectId")
110+
public void the_NSI_connection_is_queued_for_asynchronous_reservation_while_including_a_projectId() {
111+
try {
112+
queueNsiConnection(true, "ABCD-1234-EFGH-5678");
113+
} catch (Exception e) {
114+
world.add(e);
115+
log.error("NsiProviderSteps Error - {}", e);
116+
}
117+
}
118+
119+
@Given("The NSI connection is queued for asynchronous reservation while including a blank projectId")
120+
public void the_NSI_connection_is_queued_for_asynchronous_reservation_while_including_a_blank_projectId() {
121+
try {
122+
queueNsiConnection(true, "");
123+
} catch (Exception e) {
124+
world.add(e);
125+
log.error("NsiProviderSteps Error - {}", e);
126+
}
127+
}
128+
129+
@When("The NSI queue size is {}")
130+
public void the_NSI_queue_size_is(int expectedSize) {
131+
try {
132+
assert queue.queue != null && !queue.queue.isEmpty();
133+
log.info("NSI QUEUE size: {}", queue.queue.size());
134+
assert queue.queue.size() == expectedSize;
135+
} catch (Exception e) {
136+
world.add(e);
137+
log.error("NsiProviderSteps Error - {}", e);
138+
}
139+
}
140+
141+
@When("The NSI reserve is requested")
142+
public void the_NSI_reserve_is_requested() {
143+
try {
144+
queue.processQueue();
145+
} catch (Exception e) {
146+
world.add(e);
147+
log.error("NsiProviderSteps Error - {}", e);
148+
}
149+
}
150+
151+
@Then("The NSI connection does not have a projectId")
152+
public void the_NSI_connection_does_not_have_a_projectId() {
153+
try {
154+
queue.queue.forEach(element -> {
155+
prettyLog(element);
156+
});
157+
} catch (Exception e) {
158+
world.add(e);
159+
log.error("NsiProviderSteps Error - {}", e);
160+
}
161+
}
162+
163+
@Then("The NSI connection is put on hold")
164+
public void the_NSI_connection_is_put_on_hold() {
165+
try {
166+
assert connService.getHeld().size() == 1;
167+
} catch (Exception e) {
168+
world.add(e);
169+
log.error("NsiProviderSteps Error - {}", e);
170+
}
171+
}
172+
173+
@Then("The NSI connection has a projectId")
174+
public void the_NSI_connection_has_a_projectId() {
175+
try {
176+
connService.getHeld().forEach((id, conn) -> {
177+
log.info("Connection {} has projectId {}", id, conn.getProjectId());
178+
assert conn.getProjectId() != null;
179+
});
180+
} catch (Exception e) {
181+
world.add(e);
182+
log.error("NsiProviderSteps Error - {}", e);
183+
}
184+
}
185+
186+
@Then("The NSI provider encountered {int} errors")
187+
public void the_NSI_provider_encountered_errors(int errorCount) {
188+
assert nsiService.getErrorCount() == errorCount;
189+
assert world.getExceptions().size() == errorCount;
190+
}
191+
192+
private void loadTopology() throws Exception {
193+
String topoPath = "topo/esnet.json";
194+
Topology t = topoPopulator.loadTopology(topoPath);
195+
if (t == null) throw new Exception(topoPath + " is not a topology");
196+
topoService.replaceTopology(t);
197+
}
198+
199+
private void queueNsiConnection() throws Exception {
200+
queueNsiConnection(false, null);
201+
}
202+
203+
private void queueNsiConnection(boolean withProjectId, String projectIdValue) throws Exception {
204+
HttpMethod method = HttpMethod.POST;
205+
String xmlDataTemplate = "http/nsi.reserve.template.xml";
206+
Map<String, String> valuesMap = new HashMap<>();
207+
Instant now = Instant.now();
208+
209+
log.info("NOW time used is {}", now.toString());
210+
211+
Instant startTime = now;
212+
Instant endTime = now.plusSeconds(60 * 20);
213+
214+
log.info("START time will be {}", startTime.toString());
215+
log.info("END time will be {}", endTime.toString());
216+
217+
valuesMap.put("startTime", startTime.toString() );
218+
valuesMap.put("endTime", endTime.toString() );
219+
220+
if (withProjectId) {
221+
xmlDataTemplate = "http/nsi.reserve.template.xml";
222+
valuesMap.put("projectId", projectIdValue);
223+
} else {
224+
xmlDataTemplate = "http/nsi.reserve.no-project-id.template.xml";
225+
}
226+
227+
String url = "/services/provider?ServiceName=NsiProviderService&PortName=NsiProviderPort&PortTypeName=ConnectionProviderPort";
228+
InputStream bodyInputStream = new ClassPathResource(xmlDataTemplate).getInputStream();
229+
String payload = StreamUtils.copyToString(bodyInputStream, Charset.defaultCharset());
230+
231+
StringSubstitutor sub = new StringSubstitutor(valuesMap);
232+
233+
payload = sub.replace(payload);
234+
235+
response = new ResponseEntity<>("", HttpStatus.NOT_FOUND);
236+
237+
HttpHeaders headers = new HttpHeaders();
238+
headers.setContentType(MediaType.APPLICATION_XML);
239+
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
240+
241+
response = restTemplate.exchange(url, method, entity, String.class);
242+
243+
assert response.getStatusCode() == HttpStatus.OK;
244+
}
245+
246+
public void releaseAllConnections() {
247+
connService.getHeld().clear();
248+
}
249+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
cucumber.publish.quiet=true
22
cucumber.features=src/test/resources/**/*.feature
3-
cucumber.glue=net.es.oscars.cuke
3+
cucumber.glue=net.es.oscars.cuke
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<ns2:Envelope xmlns:ns6="http://www.w3.org/2000/09/xmldsig#" xmlns:ns5="http://www.w3.org/2001/04/xmlenc#" xmlns:ns8="http://schemas.ogf.org/nsi/2013/12/services/point2point" xmlns:ns7="http://schemas.ogf.org/nsi/2013/12/framework/types" xmlns:ns9="http://schemas.ogf.org/nsi/2013/12/framework/headers" xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns4="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ns3="http://schemas.ogf.org/nsi/2013/12/connection/types">
2+
<ns2:Header>
3+
<ns9:nsiHeader>
4+
<protocolVersion>application/vnd.ogf.nsi.cs.v2.provider+soap</protocolVersion>
5+
<correlationId>fae29d4f-e61e-4987-a19a-asd12</correlationId>
6+
<requesterNSA>urn:ogf:network:sense-rm.es.net:2013:esnet</requesterNSA>
7+
<providerNSA>urn:ogf:network:es.net:2013:nsa</providerNSA>
8+
<replyTo></replyTo>
9+
</ns9:nsiHeader>
10+
</ns2:Header>
11+
<ns2:Body>
12+
<ns3:reserve>
13+
14+
<description>NSI test</description>
15+
<criteria version="0">
16+
<schedule>
17+
<startTime>${startTime}</startTime>
18+
<endTime>${endTime}</endTime>
19+
</schedule>
20+
<serviceType>http://services.ogf.org/nsi/2013/12/descriptions/EVTS.A-GOLE</serviceType>
21+
<ns8:p2ps>
22+
<capacity>150000</capacity>
23+
<directionality>Bidirectional</directionality>
24+
<symmetricPath>true</symmetricPath>
25+
<sourceSTP>urn:ogf:network:es.net:2013::losa-cr6:2_1_c4_1:+?vlan=3700</sourceSTP>
26+
<destSTP>urn:ogf:network:es.net:2013::fnalfcc-cr6:lag-2:+?vlan=3700</destSTP>
27+
</ns8:p2ps>
28+
</criteria>
29+
</ns3:reserve>
30+
</ns2:Body>
31+
</ns2:Envelope>

0 commit comments

Comments
 (0)