Skip to content

Commit 979e826

Browse files
author
Andrei Kamarouski
committed
feature: stf support (without manual reserve)
1 parent f92f503 commit 979e826

31 files changed

+304
-229
lines changed

agent/pom.xml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1313
<selenium.version>4.18.1</selenium.version>
1414
<commons-lang3.version>3.14.0</commons-lang3.version>
15+
<jackson-databind.version>2.16.0</jackson-databind.version>
16+
<httpclient.version>5.2.2</httpclient.version>
17+
<genson.version>1.6</genson.version>
18+
<jersey-bundle.version>1.19.4</jersey-bundle.version>
19+
<lombok.version>1.18.30</lombok.version>
1520
<maven-shade-plugin.version>3.5.0</maven-shade-plugin.version>
1621
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
1722
</properties>
@@ -38,6 +43,42 @@
3843
<artifactId>commons-lang3</artifactId>
3944
<version>${commons-lang3.version}</version>
4045
</dependency>
46+
<dependency>
47+
<groupId>org.projectlombok</groupId>
48+
<artifactId>lombok</artifactId>
49+
<version>${lombok.version}</version>
50+
<scope>provided</scope>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.apache.commons</groupId>
54+
<artifactId>commons-lang3</artifactId>
55+
<version>${commons-lang3.version}</version>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.fasterxml.jackson.core</groupId>
59+
<artifactId>jackson-databind</artifactId>
60+
<version>${jackson-databind.version}</version>
61+
</dependency>
62+
<dependency>
63+
<groupId>org.apache.httpcomponents.client5</groupId>
64+
<artifactId>httpclient5</artifactId>
65+
<version>${httpclient.version}</version>
66+
</dependency>
67+
<dependency>
68+
<groupId>com.owlike</groupId>
69+
<artifactId>genson</artifactId>
70+
<version>${genson.version}</version>
71+
</dependency>
72+
<dependency>
73+
<groupId>com.sun.jersey</groupId>
74+
<artifactId>jersey-bundle</artifactId>
75+
<version>${jersey-bundle.version}</version>
76+
</dependency>
77+
<dependency>
78+
<groupId>jakarta.annotation</groupId>
79+
<artifactId>jakarta.annotation-api</artifactId>
80+
<version>3.0.0-M1</version>
81+
</dependency>
4182
</dependencies>
4283
<build>
4384
<plugins>

agent/src/main/java/com/zebrunner/mcloud/grid/agent/MobileCapabilityMatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import java.util.List;
2929
import java.util.logging.Logger;
3030

31-
import static com.zebrunner.mcloud.grid.agent.utils.CapabilityUtils.getAppiumCapability;
31+
import static com.zebrunner.mcloud.grid.agent.util.CapabilityUtils.getAppiumCapability;
3232

3333
@SuppressWarnings("unused")
3434
public final class MobileCapabilityMatcher extends DefaultSlotMatcher {

agent/src/main/java/com/zebrunner/mcloud/grid/agent/NodeAgent.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@
2020
public class NodeAgent {
2121
private static final Logger LOGGER = Logger.getLogger(NodeAgent.class.getName());
2222
private static final String RELAY_SESSION_FACTORY_CLASS = "org.openqa.selenium.grid.node.relay.RelaySessionFactory";
23-
private static final String TEST_METHOD_NAME = "test";
23+
private static final String SESSION_SLOT_CLASS = "org.openqa.selenium.grid.node.local.SessionSlot";
2424

2525
public static void premain(String args, Instrumentation instrumentation) {
2626
try {
2727
new AgentBuilder.Default()
2828
.with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
2929
.type(named(RELAY_SESSION_FACTORY_CLASS))
3030
.transform((builder, type, classloader, module, protectionDomain) -> addTestMethodInterceptor(builder))
31+
.type(named(SESSION_SLOT_CLASS))
32+
.transform((builder, type, classloader, module, protectionDomain) -> addSessionSlotMethodInterceptor(builder))
3133
.installOn(instrumentation);
3234
} catch (Exception e) {
3335
LOGGER.warning(() -> "Could not init instrumentation.");
@@ -39,15 +41,46 @@ private static DynamicType.Builder<?> addTestMethodInterceptor(DynamicType.Build
3941
.intercept(to(testMethodInterceptor()));
4042
}
4143

44+
private static DynamicType.Builder<?> addSessionSlotMethodInterceptor(DynamicType.Builder<?> builder) {
45+
return builder.method(isReleaseMethod())
46+
.intercept(to(releaseMethodInterceptor()))
47+
.method(isReserveMethod())
48+
.intercept(to(reserveMethodInterceptor()));
49+
}
50+
4251
public static ElementMatcher<? super MethodDescription> isTestMethod() {
4352
return isPublic()
4453
.and(not(isStatic()))
45-
.and(new NameMatcher<>(TEST_METHOD_NAME::equals));
54+
.and(new NameMatcher<>("test"::equals));
4655
}
4756

4857
private static TypeDescription testMethodInterceptor() {
4958
return TypePool.Default.ofSystemLoader()
5059
.describe(RelaySessionFactoryInterceptor.class.getName())
5160
.resolve();
5261
}
62+
63+
public static ElementMatcher<? super MethodDescription> isReleaseMethod() {
64+
return isPublic()
65+
.and(not(isStatic()))
66+
.and(new NameMatcher<>("release"::equals));
67+
}
68+
69+
private static TypeDescription releaseMethodInterceptor() {
70+
return TypePool.Default.ofSystemLoader()
71+
.describe(SessionSlotReleaseInterceptor.class.getName())
72+
.resolve();
73+
}
74+
75+
public static ElementMatcher<? super MethodDescription> isReserveMethod() {
76+
return isPublic()
77+
.and(not(isStatic()))
78+
.and(new NameMatcher<>("reserve"::equals));
79+
}
80+
81+
private static TypeDescription reserveMethodInterceptor() {
82+
return TypePool.Default.ofSystemLoader()
83+
.describe(SessionSlotReserveInterceptor.class.getName())
84+
.resolve();
85+
}
5386
}

agent/src/main/java/com/zebrunner/mcloud/grid/agent/RelaySessionFactoryInterceptor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ public static Object onTestMethodInvocation(@This final RelaySessionFactory fact
1717
@SuperCall final Callable<Object> proxy, @Argument(0) Capabilities capabilities) throws Exception {
1818
return CAPABILITY_MATCHER.matches(factory.getStereotype(), capabilities);
1919
}
20+
2021
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.zebrunner.mcloud.grid.agent;
2+
3+
import com.zebrunner.mcloud.grid.agent.stf.entity.Path;
4+
import com.zebrunner.mcloud.grid.agent.util.HttpClient;
5+
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
6+
import net.bytebuddy.implementation.bind.annotation.SuperCall;
7+
import net.bytebuddy.implementation.bind.annotation.This;
8+
import org.apache.commons.lang3.StringUtils;
9+
import org.openqa.selenium.grid.node.local.SessionSlot;
10+
11+
import java.util.concurrent.atomic.AtomicReference;
12+
import java.util.logging.Logger;
13+
14+
public class SessionSlotReleaseInterceptor {
15+
private static final Logger LOGGER = Logger.getLogger(SessionSlotReleaseInterceptor.class.getName());
16+
private static final String STF_URL = System.getenv("STF_URL");
17+
private static final String DEFAULT_STF_TOKEN = System.getenv("STF_TOKEN");
18+
private static final boolean STF_ENABLED = (!StringUtils.isEmpty(STF_URL) && !StringUtils.isEmpty(DEFAULT_STF_TOKEN));
19+
private static final String UDID = System.getenv("DEVICE_UDID");
20+
static final AtomicReference<Boolean> DISCONNECT = new AtomicReference<>(true);
21+
22+
@RuntimeType
23+
public static void onTestMethodInvocation(@This final SessionSlot slot, @SuperCall final Runnable proxy) throws Exception {
24+
if (STF_ENABLED) {
25+
try {
26+
if (DISCONNECT.getAndSet(true)) {
27+
LOGGER.info(() -> "[STF] Return STF Device.");
28+
if (HttpClient.uri(Path.STF_USER_DEVICES_BY_ID_PATH, STF_URL, UDID)
29+
.withAuthorization(buildAuthToken(DEFAULT_STF_TOKEN))
30+
.delete(Void.class).getStatus() != 200) {
31+
LOGGER.warning(() -> "[STF] Could not return device to the STF.");
32+
}
33+
}
34+
} catch (Exception e) {
35+
LOGGER.warning(() -> String.format("[STF] Could not return device to the STF. Error: %s", e.getMessage()));
36+
}
37+
}
38+
proxy.run();
39+
}
40+
41+
private static String buildAuthToken(String authToken) {
42+
return "Bearer " + authToken;
43+
}
44+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.zebrunner.mcloud.grid.agent;
2+
3+
import com.zebrunner.mcloud.grid.agent.stf.entity.Devices;
4+
import com.zebrunner.mcloud.grid.agent.stf.entity.Path;
5+
import com.zebrunner.mcloud.grid.agent.stf.entity.STFDevice;
6+
import com.zebrunner.mcloud.grid.agent.stf.entity.User;
7+
import com.zebrunner.mcloud.grid.agent.util.HttpClient;
8+
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
9+
import net.bytebuddy.implementation.bind.annotation.SuperCall;
10+
import net.bytebuddy.implementation.bind.annotation.This;
11+
import org.apache.commons.lang3.StringUtils;
12+
import org.openqa.selenium.grid.node.local.SessionSlot;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
import java.util.Optional;
17+
import java.util.concurrent.TimeUnit;
18+
import java.util.logging.Logger;
19+
20+
import static com.zebrunner.mcloud.grid.agent.SessionSlotReleaseInterceptor.DISCONNECT;
21+
22+
public class SessionSlotReserveInterceptor {
23+
24+
private static final Logger LOGGER = Logger.getLogger(SessionSlotReserveInterceptor.class.getName());
25+
private static final String NOT_AUTHENTICATED_ERROR = "[STF] Not authenticated at STF successfully! URL: '%s'; Token: '%s'";
26+
private static final String UNABLE_GET_DEVICES_STATUS_ERROR = "[STF] Unable to get devices status. HTTP status: %s";
27+
private static final String COULD_NOT_FIND_DEVICE_ERROR = "[STF] Could not find STF device with udid: %s";
28+
private static final String STF_URL = System.getenv("STF_URL");
29+
private static final String DEFAULT_STF_TOKEN = System.getenv("STF_TOKEN");
30+
private static final boolean STF_ENABLED = (!StringUtils.isEmpty(STF_URL) && !StringUtils.isEmpty(DEFAULT_STF_TOKEN));
31+
// Max time is seconds for reserving devices in STF
32+
private static final String DEFAULT_STF_TIMEOUT = Optional.ofNullable(System.getenv("STF_TIMEOUT"))
33+
.filter(StringUtils::isNotBlank)
34+
.orElse("3600");
35+
private static final String UDID = System.getenv("DEVICE_UDID");
36+
37+
@RuntimeType
38+
public static void onTestMethodInvocation(@This final SessionSlot slot, @SuperCall final Runnable proxy) throws Exception {
39+
try {
40+
if (STF_ENABLED) {
41+
// String stfToken = CapabilityUtils.getZebrunnerCapability(slot, "STF_TOKEN")
42+
// .map(String::valueOf)
43+
// .orElse(DEFAULT_STF_TOKEN);
44+
String stfToken = DEFAULT_STF_TOKEN;
45+
46+
HttpClient.Response<User> user = HttpClient.uri(Path.STF_USER_PATH, STF_URL)
47+
.withAuthorization(buildAuthToken(stfToken))
48+
.get(User.class);
49+
if (user.getStatus() != 200) {
50+
LOGGER.warning(() -> String.format(NOT_AUTHENTICATED_ERROR, STF_URL, stfToken));
51+
return;
52+
}
53+
HttpClient.Response<Devices> devices = HttpClient.uri(Path.STF_DEVICES_PATH, STF_URL)
54+
.withAuthorization(buildAuthToken(stfToken))
55+
.get(Devices.class);
56+
57+
if (devices.getStatus() != 200) {
58+
LOGGER.warning(() -> String.format(UNABLE_GET_DEVICES_STATUS_ERROR, devices.getStatus()));
59+
return;
60+
}
61+
62+
Optional<STFDevice> optionalSTFDevice = devices.getObject()
63+
.getDevices()
64+
.stream()
65+
.filter(device -> StringUtils.equals(device.getSerial(), UDID))
66+
.findFirst();
67+
if (optionalSTFDevice.isEmpty()) {
68+
LOGGER.warning(() -> String.format(COULD_NOT_FIND_DEVICE_ERROR, UDID));
69+
return;
70+
}
71+
72+
STFDevice device = optionalSTFDevice.get();
73+
LOGGER.info(() -> String.format("[STF] STF device info: %s", device));
74+
75+
boolean reserve = true;
76+
77+
if (device.getOwner() != null) {
78+
if (!(StringUtils.equals(device.getOwner().getName(), user.getObject().getUser().getName()) &&
79+
device.getPresent() &&
80+
device.getReady())) {
81+
LOGGER.warning(() -> String.format("[STF] STF device busy by %s or not present/ready.", device.getOwner().getName()));
82+
return;
83+
} else if (!StringUtils.equals(stfToken, DEFAULT_STF_TOKEN)) {
84+
DISCONNECT.set(false);
85+
LOGGER.info(() -> String.format("[STF] STF device manually reserved by the same user: %s.", device.getOwner().getName()));
86+
reserve = false;
87+
}
88+
}
89+
if (reserve) {
90+
Map<String, Object> entity = new HashMap<>();
91+
entity.put("serial", UDID);
92+
entity.put("timeout",
93+
TimeUnit.SECONDS.toMillis(Integer.parseInt(DEFAULT_STF_TIMEOUT)));
94+
if (HttpClient.uri(Path.STF_USER_DEVICES_PATH, STF_URL)
95+
.withAuthorization(buildAuthToken(stfToken))
96+
.post(Void.class, entity).getStatus() != 200) {
97+
LOGGER.warning(() -> String.format("[STF] Could not reserve STF device with udid: %s.", UDID));
98+
} else {
99+
LOGGER.info(() -> "[STF] Device successfully reserved.");
100+
}
101+
}
102+
}
103+
} catch (Exception e) {
104+
LOGGER.warning(() -> String.format("[STF] Could not reserve STF device. Error: %s", e.getMessage()));
105+
} finally {
106+
proxy.run();
107+
}
108+
}
109+
110+
private static String buildAuthToken(String authToken) {
111+
return "Bearer " + authToken;
112+
}
113+
}

src/main/java/com/zebrunner/mcloud/grid/models/stf/App.java renamed to agent/src/main/java/com/zebrunner/mcloud/grid/agent/stf/entity/App.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*******************************************************************************/
1616

17-
package com.zebrunner.mcloud.grid.models.stf;
17+
package com.zebrunner.mcloud.grid.agent.stf.entity;
1818

1919
import com.fasterxml.jackson.annotation.JsonAnyGetter;
2020
import com.fasterxml.jackson.annotation.JsonAnySetter;

src/main/java/com/zebrunner/mcloud/grid/models/stf/Battery.java renamed to agent/src/main/java/com/zebrunner/mcloud/grid/agent/stf/entity/Battery.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,19 @@
1414
* limitations under the License.
1515
*******************************************************************************/
1616

17-
package com.zebrunner.mcloud.grid.models.stf;
18-
19-
import java.util.HashMap;
20-
import java.util.Map;
17+
package com.zebrunner.mcloud.grid.agent.stf.entity;
2118

2219
import com.fasterxml.jackson.annotation.JsonAnyGetter;
2320
import com.fasterxml.jackson.annotation.JsonAnySetter;
2421
import com.fasterxml.jackson.annotation.JsonIgnore;
2522
import com.fasterxml.jackson.annotation.JsonInclude;
26-
2723
import lombok.Getter;
2824
import lombok.NoArgsConstructor;
2925
import lombok.Setter;
3026

27+
import java.util.HashMap;
28+
import java.util.Map;
29+
3130
@Getter
3231
@Setter
3332
@NoArgsConstructor

src/main/java/com/zebrunner/mcloud/grid/models/stf/Browser.java renamed to agent/src/main/java/com/zebrunner/mcloud/grid/agent/stf/entity/Browser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*******************************************************************************/
1616

17-
package com.zebrunner.mcloud.grid.models.stf;
17+
package com.zebrunner.mcloud.grid.agent.stf.entity;
1818

1919
import com.fasterxml.jackson.annotation.JsonAnyGetter;
2020
import com.fasterxml.jackson.annotation.JsonAnySetter;

src/main/java/com/zebrunner/mcloud/grid/models/stf/Device.java renamed to agent/src/main/java/com/zebrunner/mcloud/grid/agent/stf/entity/Device.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,19 @@
1414
* limitations under the License.
1515
*******************************************************************************/
1616

17-
package com.zebrunner.mcloud.grid.models.stf;
18-
19-
import java.util.HashMap;
20-
import java.util.Map;
17+
package com.zebrunner.mcloud.grid.agent.stf.entity;
2118

2219
import com.fasterxml.jackson.annotation.JsonAnyGetter;
2320
import com.fasterxml.jackson.annotation.JsonAnySetter;
2421
import com.fasterxml.jackson.annotation.JsonIgnore;
2522
import com.fasterxml.jackson.annotation.JsonInclude;
26-
2723
import lombok.Getter;
2824
import lombok.NoArgsConstructor;
2925
import lombok.Setter;
3026

27+
import java.util.HashMap;
28+
import java.util.Map;
29+
3130
@Getter
3231
@Setter
3332
@NoArgsConstructor

0 commit comments

Comments
 (0)