Skip to content

Commit bb34ffe

Browse files
Merge branch 'master' of https://github.com/RWTH-i5-IDSG/steve
2 parents 27db904 + 9a8c67d commit bb34ffe

File tree

5 files changed

+201
-70
lines changed

5 files changed

+201
-70
lines changed

src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.rwth.idsg.steve.repository.impl;
22

3+
import com.google.common.util.concurrent.Striped;
34
import de.rwth.idsg.steve.SteveException;
45
import de.rwth.idsg.steve.ocpp.OcppProtocol;
56
import de.rwth.idsg.steve.ocpp.OcppTransport;
@@ -31,6 +32,7 @@
3132
import java.util.Collections;
3233
import java.util.List;
3334
import java.util.Map;
35+
import java.util.concurrent.locks.Lock;
3436
import java.util.stream.Collectors;
3537

3638
import static de.rwth.idsg.steve.SteveConfiguration.CONFIG;
@@ -49,6 +51,8 @@
4951
public class ChargePointRepositoryImpl implements ChargePointRepository {
5052

5153
private final boolean autoRegisterUnknownStations = CONFIG.getOcpp().isAutoRegisterUnknownStations();
54+
private final Striped<Lock> isRegisteredLocks = Striped.lock(16);
55+
5256
private final DSLContext ctx;
5357
private final AddressRepository addressRepository;
5458

@@ -60,24 +64,30 @@ public ChargePointRepositoryImpl(DSLContext ctx, AddressRepository addressReposi
6064

6165
@Override
6266
public boolean isRegistered(String chargeBoxId) {
63-
// 1. exit if already registered
64-
if (isRegisteredInternal(chargeBoxId)) {
65-
return true;
66-
}
67+
Lock l = isRegisteredLocks.get(chargeBoxId);
68+
l.lock();
69+
try {
70+
// 1. exit if already registered
71+
if (isRegisteredInternal(chargeBoxId)) {
72+
return true;
73+
}
6774

68-
// 2. ok, this chargeBoxId is unknown. exit if auto-register is disabled
69-
if (!autoRegisterUnknownStations) {
70-
return false;
71-
}
75+
// 2. ok, this chargeBoxId is unknown. exit if auto-register is disabled
76+
if (!autoRegisterUnknownStations) {
77+
return false;
78+
}
7279

73-
// 3. chargeBoxId is unknown and auto-register is enabled. insert chargeBoxId
74-
try {
75-
addChargePoint(Collections.singletonList(chargeBoxId));
76-
log.warn("Auto-registered unknown chargebox '{}'", chargeBoxId);
77-
return true;
78-
} catch (Exception e) {
79-
log.error("Failed to auto-register unknown chargebox '" + chargeBoxId + "'", e);
80-
return false;
80+
// 3. chargeBoxId is unknown and auto-register is enabled. insert chargeBoxId
81+
try {
82+
addChargePoint(Collections.singletonList(chargeBoxId));
83+
log.warn("Auto-registered unknown chargebox '{}'", chargeBoxId);
84+
return true;
85+
} catch (Exception e) {
86+
log.error("Failed to auto-register unknown chargebox '" + chargeBoxId + "'", e);
87+
return false;
88+
}
89+
} finally {
90+
l.unlock();
8191
}
8292
}
8393

src/main/java/de/rwth/idsg/steve/service/UnidentifiedIncomingObjectService.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import com.google.common.cache.Cache;
44
import com.google.common.cache.CacheBuilder;
55
import de.rwth.idsg.steve.service.dto.UnidentifiedIncomingObject;
6+
import lombok.extern.slf4j.Slf4j;
67

78
import java.util.Comparator;
89
import java.util.List;
10+
import java.util.concurrent.ExecutionException;
911
import java.util.stream.Collectors;
1012

1113
/**
@@ -14,6 +16,7 @@
1416
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
1517
* @since 20.03.2018
1618
*/
19+
@Slf4j
1720
public class UnidentifiedIncomingObjectService {
1821

1922
private final Cache<String, UnidentifiedIncomingObject> objectsHolder;
@@ -33,13 +36,11 @@ public List<UnidentifiedIncomingObject> getObjects() {
3336
}
3437

3538
public void processNewUnidentified(String key) {
36-
synchronized (objectsHolder) {
37-
UnidentifiedIncomingObject value = objectsHolder.getIfPresent(key);
38-
if (value == null) {
39-
objectsHolder.put(key, new UnidentifiedIncomingObject(key));
40-
} else {
41-
value.updateStats();
42-
}
39+
try {
40+
objectsHolder.get(key, () -> new UnidentifiedIncomingObject(key))
41+
.updateStats();
42+
} catch (ExecutionException e) {
43+
log.error("Error occurred", e);
4344
}
4445
}
4546
}

src/main/java/de/rwth/idsg/steve/service/dto/UnidentifiedIncomingObject.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ public class UnidentifiedIncomingObject {
1818

1919
public UnidentifiedIncomingObject(String key) {
2020
this.key = key;
21-
updateStats();
2221
}
2322

24-
public void updateStats() {
23+
public synchronized void updateStats() {
2524
numberOfAttempts++;
2625
lastAttemptTimestamp = DateTime.now();
2726
}

src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java

Lines changed: 156 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,206 @@
22

33
import de.rwth.idsg.steve.utils.StressTester;
44
import de.rwth.idsg.steve.utils.__DatabasePreparer__;
5-
import lombok.extern.slf4j.Slf4j;
65
import ocpp.cs._2015._10.AuthorizationStatus;
76
import ocpp.cs._2015._10.AuthorizeRequest;
87
import ocpp.cs._2015._10.AuthorizeResponse;
98
import ocpp.cs._2015._10.BootNotificationRequest;
109
import ocpp.cs._2015._10.BootNotificationResponse;
1110
import ocpp.cs._2015._10.CentralSystemService;
11+
import ocpp.cs._2015._10.ChargePointErrorCode;
12+
import ocpp.cs._2015._10.ChargePointStatus;
13+
import ocpp.cs._2015._10.HeartbeatRequest;
14+
import ocpp.cs._2015._10.HeartbeatResponse;
15+
import ocpp.cs._2015._10.MeterValue;
16+
import ocpp.cs._2015._10.MeterValuesRequest;
17+
import ocpp.cs._2015._10.MeterValuesResponse;
1218
import ocpp.cs._2015._10.RegistrationStatus;
13-
import org.junit.After;
14-
import org.junit.AfterClass;
19+
import ocpp.cs._2015._10.SampledValue;
20+
import ocpp.cs._2015._10.StartTransactionRequest;
21+
import ocpp.cs._2015._10.StartTransactionResponse;
22+
import ocpp.cs._2015._10.StatusNotificationRequest;
23+
import ocpp.cs._2015._10.StatusNotificationResponse;
24+
import ocpp.cs._2015._10.StopTransactionRequest;
25+
import ocpp.cs._2015._10.StopTransactionResponse;
26+
import org.joda.time.DateTime;
1527
import org.junit.Assert;
16-
import org.junit.Before;
17-
import org.junit.BeforeClass;
18-
import org.junit.Test;
28+
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.List;
32+
import java.util.concurrent.ThreadLocalRandom;
1933

2034
import static de.rwth.idsg.steve.utils.Helpers.getForOcpp16;
2135
import static de.rwth.idsg.steve.utils.Helpers.getPath;
2236
import static de.rwth.idsg.steve.utils.Helpers.getRandomString;
37+
import static de.rwth.idsg.steve.utils.Helpers.getRandomStrings;
2338

2439
/**
2540
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
2641
* @since 18.04.2018
2742
*/
28-
@Slf4j
2943
public class StressTestSoapOCPP16 {
3044

31-
private static final String REGISTERED_CHARGE_BOX_ID = __DatabasePreparer__.getRegisteredChargeBoxId();
45+
// higher values -> more stress
46+
//
47+
private static final int THREAD_COUNT = 50;
48+
private static final int REPEAT_COUNT_PER_THREAD = 50;
49+
50+
// lower values -> more stress
51+
//
52+
// reason: these only specify the size of the values "bag" from which a test picks a value randomly. if there is
53+
// less values to pick from, it is more likely that tests will use the same value at the same time. this produces
54+
// more overhead for steve (especially db) when multiple threads "fight" for inserting/updating a db row/cell.
55+
//
56+
private static final int ID_TAG_COUNT = 50;
57+
private static final int CHARGE_BOX_COUNT = 100;
58+
private static final int CONNECTOR_COUNT_PER_CHARGE_BOX = 25;
3259

3360
private static final String path = getPath();
34-
private static Application app;
3561

36-
@BeforeClass
37-
public static void initClass() throws Exception {
62+
public static void main(String[] args) throws Exception {
63+
new StressTestSoapOCPP16().attack();
64+
}
65+
66+
private void attack() throws Exception {
3867
Assert.assertEquals(ApplicationProfile.TEST, SteveConfiguration.CONFIG.getProfile());
3968
Assert.assertTrue(SteveConfiguration.CONFIG.getOcpp().isAutoRegisterUnknownStations());
4069

41-
app = new Application();
42-
app.start();
43-
}
70+
__DatabasePreparer__.prepare();
4471

45-
@AfterClass
46-
public static void destroyClass() throws Exception {
47-
if (app != null) {
48-
app.stop();
72+
Application app = new Application();
73+
try {
74+
app.start();
75+
attackInternal();
76+
} finally {
77+
try {
78+
app.stop();
79+
} finally {
80+
__DatabasePreparer__.cleanUp();
81+
}
4982
}
5083
}
5184

52-
@Before
53-
public void init() {
54-
__DatabasePreparer__.prepare();
55-
}
85+
private static void attackInternal() throws Exception {
86+
final List<String> idTags = getRandomStrings(ID_TAG_COUNT);
87+
final List<String> chargeBoxIds = getRandomStrings(CHARGE_BOX_COUNT);
5688

57-
@After
58-
public void destroy() {
59-
__DatabasePreparer__.cleanUp();
60-
}
89+
StressTester tester = new StressTester(THREAD_COUNT, REPEAT_COUNT_PER_THREAD);
6190

62-
@Test
63-
public void testBootNotification() throws Exception {
64-
StressTester tester = new StressTester(50, 5);
91+
tester.test(() -> {
92+
CentralSystemService client = getForOcpp16(path);
93+
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
6594

66-
CentralSystemService client = getForOcpp16(path);
95+
String idTag = idTags.get(localRandom.nextInt(idTags.size()));
96+
String chargeBoxId = chargeBoxIds.get(localRandom.nextInt(chargeBoxIds.size()));
97+
int connectorId = localRandom.nextInt(1, CONNECTOR_COUNT_PER_CHARGE_BOX + 1);
6798

68-
tester.test(() -> {
99+
int transactionStart = localRandom.nextInt(0, Integer.MAX_VALUE);
100+
int transactionStop = localRandom.nextInt(transactionStart + 1, Integer.MAX_VALUE);
101+
102+
// to insert chargeBoxId into db
69103
BootNotificationResponse boot = client.bootNotification(
70104
new BootNotificationRequest()
71105
.withChargePointVendor(getRandomString())
72106
.withChargePointModel(getRandomString()),
73-
getRandomString());
107+
chargeBoxId);
74108
Assert.assertEquals(RegistrationStatus.ACCEPTED, boot.getStatus());
109+
110+
HeartbeatResponse heartbeat = client.heartbeat(
111+
new HeartbeatRequest(),
112+
chargeBoxId
113+
);
114+
Assert.assertNotNull(heartbeat);
115+
116+
for (int i = 0; i <= CONNECTOR_COUNT_PER_CHARGE_BOX; i++) {
117+
StatusNotificationResponse status = client.statusNotification(
118+
new StatusNotificationRequest()
119+
.withErrorCode(ChargePointErrorCode.NO_ERROR)
120+
.withStatus(ChargePointStatus.AVAILABLE)
121+
.withConnectorId(i)
122+
.withTimestamp(DateTime.now()),
123+
chargeBoxId
124+
);
125+
Assert.assertNotNull(status);
126+
}
127+
128+
AuthorizeResponse auth = client.authorize(
129+
new AuthorizeRequest().withIdTag(idTag),
130+
chargeBoxId
131+
);
132+
Assert.assertNotEquals(AuthorizationStatus.ACCEPTED, auth.getIdTagInfo().getStatus());
133+
134+
StartTransactionResponse start = client.startTransaction(
135+
new StartTransactionRequest()
136+
.withConnectorId(connectorId)
137+
.withIdTag(idTag)
138+
.withTimestamp(DateTime.now())
139+
.withMeterStart(transactionStart),
140+
chargeBoxId
141+
);
142+
Assert.assertNotNull(start);
143+
144+
StatusNotificationResponse statusStart = client.statusNotification(
145+
new StatusNotificationRequest()
146+
.withErrorCode(ChargePointErrorCode.NO_ERROR)
147+
.withStatus(ChargePointStatus.CHARGING)
148+
.withConnectorId(connectorId)
149+
.withTimestamp(DateTime.now()),
150+
chargeBoxId
151+
);
152+
Assert.assertNotNull(statusStart);
153+
154+
MeterValuesResponse meter = client.meterValues(
155+
new MeterValuesRequest()
156+
.withConnectorId(connectorId)
157+
.withTransactionId(start.getTransactionId())
158+
.withMeterValue(getMeterValues(transactionStart, transactionStop)),
159+
chargeBoxId
160+
);
161+
Assert.assertNotNull(meter);
162+
163+
StopTransactionResponse stop = client.stopTransaction(
164+
new StopTransactionRequest()
165+
.withTransactionId(start.getTransactionId())
166+
.withTimestamp(DateTime.now())
167+
.withIdTag(idTag)
168+
.withMeterStop(transactionStop),
169+
chargeBoxId
170+
);
171+
Assert.assertNotNull(stop);
172+
173+
StatusNotificationResponse statusStop = client.statusNotification(
174+
new StatusNotificationRequest()
175+
.withErrorCode(ChargePointErrorCode.NO_ERROR)
176+
.withStatus(ChargePointStatus.AVAILABLE)
177+
.withConnectorId(connectorId)
178+
.withTimestamp(DateTime.now()),
179+
chargeBoxId
180+
);
181+
Assert.assertNotNull(statusStop);
75182
});
76183

77184
tester.shutDown();
78185
}
79186

80-
@Test
81-
public void testAuthorize() throws Exception {
82-
StressTester tester = new StressTester(100, 10);
83-
84-
CentralSystemService client = getForOcpp16(path);
187+
private static List<MeterValue> getMeterValues(int transactionStart, int transactionStop) {
188+
final int size = 4;
189+
int delta = (transactionStop - transactionStart) / size;
190+
if (delta == 0) {
191+
return Collections.emptyList();
192+
}
85193

86-
tester.test(() -> {
87-
AuthorizeResponse auth = client.authorize(
88-
new AuthorizeRequest().withIdTag(getRandomString()),
89-
REGISTERED_CHARGE_BOX_ID);
90-
Assert.assertEquals(AuthorizationStatus.INVALID, auth.getIdTagInfo().getStatus());
91-
});
194+
List<MeterValue> list = new ArrayList<>(size);
195+
for (int i = 0; i < size; i++) {
196+
int meterValue = transactionStart + delta * (i + 1);
197+
list.add(createMeterValue(meterValue));
198+
}
199+
return list;
200+
}
92201

93-
tester.shutDown();
202+
private static MeterValue createMeterValue(int val) {
203+
return new MeterValue().withTimestamp(DateTime.now())
204+
.withSampledValue(new SampledValue().withValue(Integer.toString(val)));
94205
}
95206

96-
}
207+
}

src/test/java/de/rwth/idsg/steve/utils/Helpers.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.apache.cxf.ws.addressing.WSAddressingFeature;
55

66
import javax.xml.ws.soap.SOAPBinding;
7+
import java.util.ArrayList;
8+
import java.util.List;
79
import java.util.UUID;
810

911
import static de.rwth.idsg.steve.SteveConfiguration.CONFIG;
@@ -18,6 +20,14 @@ public static String getRandomString() {
1820
return UUID.randomUUID().toString();
1921
}
2022

23+
public static List<String> getRandomStrings(int size) {
24+
List<String> list = new ArrayList<>(size);
25+
for (int i = 0; i < size; i++) {
26+
list.add(getRandomString());
27+
}
28+
return list;
29+
}
30+
2131
public static String getPath() {
2232
if (CONFIG.getJetty().isHttpEnabled()) {
2333
return "http://"

0 commit comments

Comments
 (0)