Skip to content

Commit 71459f4

Browse files
authored
Merge pull request #577 from rundeck/enh/fix-sys-info-npe
RUN-3160: fix: NPE if system info does not return all data
2 parents c689212 + 839c721 commit 71459f4

File tree

4 files changed

+144
-87
lines changed

4 files changed

+144
-87
lines changed

rd-api-client/src/main/java/org/rundeck/client/api/model/sysinfo/SystemStats.java

Lines changed: 31 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818

1919
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2020
import com.fasterxml.jackson.annotation.JsonInclude;
21+
import lombok.Getter;
22+
import lombok.Setter;
2123

2224
import java.util.HashMap;
2325
import java.util.Map;
2426

27+
@Setter
28+
@Getter
2529
@JsonIgnoreProperties(ignoreUnknown = true)
2630
@JsonInclude(JsonInclude.Include.NON_NULL)
2731
public class SystemStats {
@@ -37,90 +41,36 @@ public class SystemStats {
3741

3842
public Map<String, Object> toMap() {
3943
Map<String, Object> data = new HashMap<>();
40-
data.put("timestamp", timestamp);
41-
data.put("rundeck", rundeck);
42-
data.put("executions", executions);
43-
data.put("os", os);
44-
data.put("jvm", jvm);
45-
data.put("stats", stats);
46-
data.put("metrics", metrics.toMap());
47-
data.put("threadDump", threadDump.toMap());
48-
data.put("healthcheck", healthcheck.toMap());
44+
if(null!=timestamp) {
45+
data.put("timestamp", timestamp);
46+
}
47+
if(null!=rundeck) {
48+
data.put("rundeck", rundeck);
49+
}
50+
if(null!=executions) {
51+
data.put("executions", executions);
52+
}
53+
if(null!=os) {
54+
data.put("os", os);
55+
}
56+
if (jvm != null) {
57+
data.put("jvm", jvm);
58+
}
59+
if (stats != null) {
60+
data.put("stats", stats);
61+
}
62+
if (metrics != null) {
63+
data.put("metrics", metrics.toMap());
64+
}
65+
if (threadDump != null) {
66+
data.put("threadDump", threadDump.toMap());
67+
}
68+
if (healthcheck != null) {
69+
data.put("healthcheck", healthcheck.toMap());
70+
}
4971
return data;
5072
}
5173

52-
public Map<String, Object> getTimestamp() {
53-
return timestamp;
54-
}
55-
56-
public void setTimestamp(Map<String, Object> timestamp) {
57-
this.timestamp = timestamp;
58-
}
59-
60-
public Map<String, Object> getRundeck() {
61-
return rundeck;
62-
}
63-
64-
public void setRundeck(Map<String, Object> rundeck) {
65-
this.rundeck = rundeck;
66-
}
67-
68-
public Map<String, Object> getExecutions() {
69-
return executions;
70-
}
71-
72-
public void setExecutions(Map<String, Object> executions) {
73-
this.executions = executions;
74-
}
75-
76-
public Map<String, Object> getOs() {
77-
return os;
78-
}
79-
80-
public void setOs(Map<String, Object> os) {
81-
this.os = os;
82-
}
83-
84-
public Map<String, Object> getJvm() {
85-
return jvm;
86-
}
87-
88-
public void setJvm(Map<String, Object> jvm) {
89-
this.jvm = jvm;
90-
}
91-
92-
public Map<String, Map> getStats() {
93-
return stats;
94-
}
95-
96-
public void setStats(Map<String, Map> stats) {
97-
this.stats = stats;
98-
}
99-
100-
public Link getMetrics() {
101-
return metrics;
102-
}
103-
104-
public void setMetrics(Link metrics) {
105-
this.metrics = metrics;
106-
}
107-
108-
public Link getThreadDump() {
109-
return threadDump;
110-
}
111-
112-
public void setThreadDump(Link threadDump) {
113-
this.threadDump = threadDump;
114-
}
115-
116-
public Link getHealthcheck() {
117-
return healthcheck;
118-
}
119-
120-
public void setHealthcheck(Link healthcheck) {
121-
this.healthcheck = healthcheck;
122-
}
123-
12474
@Override
12575
public String toString() {
12676
return "{" + "\n" +

rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/system/Mode.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import retrofit2.Call;
1515

1616
import java.io.IOException;
17+
import java.util.Map;
1718
import java.util.function.Function;
1819

1920
/**
@@ -26,8 +27,7 @@ public class Mode extends BaseCommand {
2627

2728

2829
@Getter @Setter
29-
static
30-
class ModeInfo {
30+
public static class ModeInfo {
3131

3232
@CommandLine.Option(names = {"-A", "--testactive"},
3333
description = "Test whether the execution mode is active: fail if not")
@@ -46,7 +46,11 @@ public int info(@CommandLine.Mixin ModeInfo opts) throws IOException, InputError
4646
throw new InputError("--testactive and --testpassive cannot be combined");
4747
}
4848
SystemInfo systemInfo = apiCall(RundeckApi::systemInfo);
49-
Object executionMode = systemInfo.system.getExecutions().get("executionMode");
49+
Map<String, Object> executions = systemInfo.system.getExecutions();
50+
if (null == executions) {
51+
throw new RuntimeException("Execution Mode was not available in System Info data. Note: Authorization system:read is required for most System Info data.");
52+
}
53+
Object executionMode = executions.get("executionMode");
5054
boolean modeIsActive = "active".equals(executionMode);
5155
boolean testpass = true;
5256
String message = "Execution Mode is currently:";
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.rundeck.client.tool.commands
2+
3+
import org.rundeck.client.api.RundeckApi
4+
import org.rundeck.client.api.model.SystemInfo
5+
import org.rundeck.client.api.model.sysinfo.SystemStats
6+
import org.rundeck.client.testing.MockRdTool
7+
import org.rundeck.client.tool.CommandOutput
8+
import org.rundeck.client.tool.RdApp
9+
import org.rundeck.client.util.Client
10+
import org.rundeck.client.util.RdClientConfig
11+
import retrofit2.Retrofit
12+
import retrofit2.mock.Calls
13+
import spock.lang.Specification
14+
import spock.lang.Unroll
15+
16+
class RDSystemSpec extends Specification {
17+
18+
@Unroll
19+
def "system info data is minimal"() {
20+
21+
given: "system info response is minimal"
22+
23+
def api = Mock(RundeckApi)
24+
25+
def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build()
26+
def client = new Client(api, retrofit, null, null, 18, true, null)
27+
def hasclient = Mock(RdApp) {
28+
getClient() >> client
29+
getAppConfig() >> Mock(RdClientConfig)
30+
}
31+
RDSystem cmd = new RDSystem()
32+
def rdtool = new MockRdTool(client: client, rdApp: hasclient)
33+
cmd.rdTool = rdtool
34+
def out = Mock(CommandOutput)
35+
cmd.rdOutput = out
36+
when: "system info is called"
37+
cmd.info()
38+
then: "api call has correct values"
39+
1 * api.systemInfo() >> Calls.response(new SystemInfo(system: new SystemStats((Map) statsData)))
40+
1 * out.output(expected)
41+
42+
0 * api._(*_)
43+
44+
where:
45+
statsData | expected
46+
[:] | [:]
47+
[rundeck: [some: 'data']] | [rundeck: [some: 'data']]
48+
}
49+
}

rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/system/ModeSpec.groovy

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package org.rundeck.client.tool.commands.system
22

3-
import groovy.transform.CompileStatic
3+
44
import org.rundeck.client.api.RundeckApi
55
import org.rundeck.client.api.model.ExecutionMode
6+
import org.rundeck.client.api.model.SystemInfo
67
import org.rundeck.client.api.model.SystemMode
8+
import org.rundeck.client.api.model.sysinfo.SystemStats
79
import org.rundeck.client.testing.MockRdTool
810
import org.rundeck.client.tool.CommandOutput
911
import org.rundeck.client.tool.RdApp
10-
import org.rundeck.client.tool.commands.enterprise.api.EnterpriseApi
11-
import org.rundeck.client.tool.commands.enterprise.api.model.EnterpriseModeResponse
1212
import org.rundeck.client.tool.extension.RdTool
1313
import org.rundeck.client.tool.options.QuietOption
1414
import org.rundeck.client.util.Client
@@ -31,6 +31,60 @@ class ModeSpec extends Specification {
3131
rdTool
3232
}
3333

34+
def "mode info when system info has mode data"() {
35+
def api = Mock(RundeckApi)
36+
def out = Mock(CommandOutput)
37+
RdTool rdTool = setupMock(api, out, 41)
38+
Mode command = new Mode()
39+
command.rdOutput = out
40+
command.rdTool = rdTool
41+
def opts = new Mode.ModeInfo()
42+
opts.testActive = testActive
43+
opts.testPassive = testPassive
44+
45+
when: "system info has execution mode data"
46+
def result = command.info(opts)
47+
48+
then:
49+
1 * api.systemInfo() >> Calls.response(new SystemInfo(system: new SystemStats((Map) statsData)))
50+
1 * out.output(statsData.executions.executionMode)
51+
(1-expected) * out.info('Execution Mode is currently:')
52+
(expected) * out.warning('Execution Mode is currently:')
53+
0 * api._(*_)
54+
result == expected
55+
where:
56+
statsData | testActive | testPassive | expected
57+
[executions: [executionMode: 'active']] | true | false | 0
58+
[executions: [executionMode: 'active']] | false | true | 1
59+
[executions: [executionMode: 'passive']] | true | false | 1
60+
[executions: [executionMode: 'passive']] | false | true | 0
61+
[executions: [executionMode: 'other']] | true | false | 1
62+
[executions: [executionMode: 'other']] | false | true | 0
63+
}
64+
def "mode info when system info has no mode data"() {
65+
def api = Mock(RundeckApi)
66+
def out = Mock(CommandOutput)
67+
RdTool rdTool = setupMock(api, out, 41)
68+
Mode command = new Mode()
69+
command.rdOutput = out
70+
command.rdTool = rdTool
71+
def opts = new Mode.ModeInfo()
72+
opts.testActive = true
73+
opts.testPassive = false
74+
75+
when: "system info has execution mode data"
76+
def result = command.info(opts)
77+
78+
then:
79+
1 * api.systemInfo() >> Calls.response(new SystemInfo(system: new SystemStats((Map) statsData)))
80+
0 * out._(*_)
81+
0 * api._(*_)
82+
RuntimeException e = thrown()
83+
84+
where:
85+
statsData = [:]
86+
}
87+
3488
def "passive"() {
3589
def api = Mock(RundeckApi)
3690
def out = Mock(CommandOutput)

0 commit comments

Comments
 (0)