Jian Li
Committed by Ray Milkey

[ONOS-4438] Add REST API for mastership service

- Correct some typos in mastership codec

Change-Id: If8a0127d8d897d4b87cae71a194dfece6aa14f49
...@@ -39,7 +39,7 @@ public final class RoleInfoCodec extends JsonCodec<RoleInfo> { ...@@ -39,7 +39,7 @@ public final class RoleInfoCodec extends JsonCodec<RoleInfo> {
39 private static final String MASTER = "master"; 39 private static final String MASTER = "master";
40 private static final String BACKUPS = "backups"; 40 private static final String BACKUPS = "backups";
41 41
42 - private static final String MISSING_MEMBER_MESSAGE = " member is required in MastershipTerm"; 42 + private static final String MISSING_MEMBER_MESSAGE = " member is required in RoleInfo";
43 43
44 @Override 44 @Override
45 public ObjectNode encode(RoleInfo roleInfo, CodecContext context) { 45 public ObjectNode encode(RoleInfo roleInfo, CodecContext context) {
......
...@@ -49,7 +49,8 @@ public class CoreWebApplication extends AbstractWebApplication { ...@@ -49,7 +49,8 @@ public class CoreWebApplication extends AbstractWebApplication {
49 DeviceKeyWebResource.class, 49 DeviceKeyWebResource.class,
50 RegionsWebResource.class, 50 RegionsWebResource.class,
51 TenantWebResource.class, 51 TenantWebResource.class,
52 - VirtualNetworkWebResource.class 52 + VirtualNetworkWebResource.class,
53 + MastershipWebResource.class
53 ); 54 );
54 } 55 }
55 } 56 }
......
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.rest.resources;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.node.ArrayNode;
20 +import com.fasterxml.jackson.databind.node.ObjectNode;
21 +import org.onosproject.cluster.NodeId;
22 +import org.onosproject.cluster.RoleInfo;
23 +import org.onosproject.mastership.MastershipAdminService;
24 +import org.onosproject.mastership.MastershipService;
25 +import org.onosproject.net.DeviceId;
26 +import org.onosproject.net.MastershipRole;
27 +import org.onosproject.rest.AbstractWebResource;
28 +
29 +import javax.ws.rs.Consumes;
30 +import javax.ws.rs.GET;
31 +import javax.ws.rs.PUT;
32 +import javax.ws.rs.Path;
33 +import javax.ws.rs.PathParam;
34 +import javax.ws.rs.Produces;
35 +import javax.ws.rs.core.MediaType;
36 +import javax.ws.rs.core.Response;
37 +import java.io.IOException;
38 +import java.io.InputStream;
39 +import java.util.Set;
40 +import java.util.concurrent.CompletableFuture;
41 +import java.util.concurrent.ExecutionException;
42 +
43 +import static org.onlab.util.Tools.nullIsNotFound;
44 +
45 +/**
46 + * Manage the mastership of ONOS instances.
47 + */
48 +@Path("mastership")
49 +public final class MastershipWebResource extends AbstractWebResource {
50 +
51 + private static final String NODE = "node";
52 + private static final String DEVICES = "devices";
53 + private static final String DEVICE_ID = "deviceId";
54 + private static final String NODE_ID = "nodeId";
55 +
56 + private static final String DEVICE_ID_INVALID = "Invalid deviceId for setting role";
57 + private static final String NODE_ID_INVALID = "Invalid nodeId for setting role";
58 +
59 + private static final String NODE_ID_NOT_FOUND = "Node id is not found";
60 + private static final String ROLE_INFO_NOT_FOUND = "Role info is not found";
61 + private static final String MASTERSHIP_ROLE_NOT_FOUND = "Mastership role is not found";
62 + private static final String RESULT_NOT_FOUND = "Result is not found";
63 +
64 + private final MastershipService mastershipService = get(MastershipService.class);
65 + private final MastershipAdminService mastershipAdminService =
66 + get(MastershipAdminService.class);
67 +
68 + /**
69 + * Returns the role of the local node for the specified device.
70 + *
71 + * @param deviceId device identifier
72 + * @return role of the current node
73 + * @onos.rsModel MastershipRole
74 + */
75 + @GET
76 + @Produces(MediaType.APPLICATION_JSON)
77 + @Path("{deviceId}/local")
78 + public Response getLocalRole(@PathParam("deviceId") String deviceId) {
79 + MastershipRole role = mastershipService.getLocalRole(DeviceId.deviceId(deviceId));
80 + ObjectNode root = codec(MastershipRole.class).encode(role, this);
81 + return ok(root).build();
82 + }
83 +
84 + /**
85 + * Returns the current master for a given device.
86 + *
87 + * @param deviceId device identifier
88 + * @return the identifier of the master controller for the device
89 + * // TODO: add swagger doc
90 + */
91 + @GET
92 + @Produces(MediaType.APPLICATION_JSON)
93 + @Path("{deviceId}/master")
94 + public Response getMasterFor(@PathParam("deviceId") String deviceId) {
95 + NodeId id = nullIsNotFound(mastershipService.getMasterFor(
96 + DeviceId.deviceId(deviceId)), NODE_ID_NOT_FOUND);
97 +
98 + ObjectNode root = mapper().createObjectNode();
99 + root.put(NODE, id.id());
100 + return ok(root).build();
101 + }
102 +
103 + /**
104 + * Returns controllers connected to a given device, in order of
105 + * preference. The first entry in the list is the current master.
106 + *
107 + * @param deviceId device identifier
108 + * @return a list of controller identifiers
109 + * @onos.rsModel RoleInfo
110 + */
111 + @GET
112 + @Produces(MediaType.APPLICATION_JSON)
113 + @Path("{deviceId}/role")
114 + public Response getNodesFor(@PathParam("deviceId") String deviceId) {
115 + RoleInfo info = nullIsNotFound(mastershipService.getNodesFor(
116 + DeviceId.deviceId(deviceId)), ROLE_INFO_NOT_FOUND);
117 + ObjectNode root = codec(RoleInfo.class).encode(info, this);
118 + return ok(root).build();
119 + }
120 +
121 + /**
122 + * Returns the devices for which a controller is master.
123 + *
124 + * @param nodeId controller identifier
125 + * @return a set of device identifiers
126 + * // TODO: add swagger doc
127 + */
128 + @GET
129 + @Produces(MediaType.APPLICATION_JSON)
130 + @Path("{nodeId}/device")
131 + public Response getDeviceOf(@PathParam("nodeId") String nodeId) {
132 + ObjectNode root = mapper().createObjectNode();
133 + ArrayNode devicesNode = root.putArray(DEVICES);
134 +
135 + Set<DeviceId> devices = mastershipService.getDevicesOf(NodeId.nodeId(nodeId));
136 + if (devices != null) {
137 + devices.forEach(id -> devicesNode.add(id.toString()));
138 + }
139 +
140 + return ok(root).build();
141 + }
142 +
143 + /**
144 + * Returns the mastership status of the local controller for a given
145 + * device forcing master selection if necessary.
146 + *
147 + * @param deviceId device identifier
148 + * @return the role of this controller instance
149 + * @onos.rsModel MastershipRole
150 + */
151 + @GET
152 + @Produces(MediaType.APPLICATION_JSON)
153 + @Path("{deviceId}/request")
154 + public Response requestRoleFor(@PathParam("deviceId") String deviceId) {
155 +
156 + // TODO: will not use CompletableFuture when MastershipService
157 + // provides a non CompletableFuture object as an output
158 + CompletableFuture<MastershipRole> result =
159 + nullIsNotFound(mastershipService.requestRoleFor(
160 + DeviceId.deviceId(deviceId)), MASTERSHIP_ROLE_NOT_FOUND);
161 +
162 + try {
163 + MastershipRole role = result.get();
164 + ObjectNode root = codec(MastershipRole.class).encode(role, this);
165 + return ok(root).build();
166 + } catch (InterruptedException | ExecutionException e) {
167 + throw new IllegalArgumentException(e);
168 + }
169 + }
170 +
171 + /**
172 + * Abandons mastership of the specified device on the local node thus
173 + * forcing selection of a new master. If the local node is not a master
174 + * for this device, no master selection will occur.
175 + *
176 + * @param deviceId device identifier
177 + * @return status of the request
178 + */
179 + @GET
180 + @Produces(MediaType.APPLICATION_JSON)
181 + @Path("{deviceId}/relinquish")
182 + public Response relinquishMastership(@PathParam("deviceId") String deviceId) {
183 + DeviceId id = DeviceId.deviceId(deviceId);
184 +
185 + // TODO: will not use CompletableFuture when MastershipService
186 + // provides a non CompletableFuture object as an output
187 + CompletableFuture<Void> result =
188 + nullIsNotFound(mastershipService.relinquishMastership(id), RESULT_NOT_FOUND);
189 +
190 + try {
191 + result.get();
192 + return Response.created(id.uri()).build();
193 + } catch (InterruptedException | ExecutionException e) {
194 + throw new IllegalArgumentException(e);
195 + }
196 + }
197 +
198 + /**
199 + * Applies the current mastership role for the specified device.
200 + *
201 + * @param stream JSON representation of device, node, mastership info
202 + * @return status of the request - CREATED if the JSON is correct,
203 + * BAD_REQUEST if the JSON is invalid
204 + * @onos.rsModel MastershipPut
205 + */
206 + @PUT
207 + @Consumes(MediaType.APPLICATION_JSON)
208 + @Produces(MediaType.APPLICATION_JSON)
209 + public Response setRole(InputStream stream) {
210 +
211 + try {
212 + ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
213 + JsonNode deviceIdJson = jsonTree.get(DEVICE_ID);
214 + JsonNode nodeIdJson = jsonTree.get(NODE_ID);
215 + MastershipRole role = codec(MastershipRole.class).decode(jsonTree, this);
216 +
217 + if (deviceIdJson == null) {
218 + throw new IllegalArgumentException(DEVICE_ID_INVALID);
219 + }
220 +
221 + if (nodeIdJson == null) {
222 + throw new IllegalArgumentException(NODE_ID_INVALID);
223 + }
224 +
225 + // TODO: will not use CompletableFuture when MastershipAdminService
226 + // provides a non CompletableFuture object as an output
227 + CompletableFuture<Void> result =
228 + nullIsNotFound(mastershipAdminService.setRole(NodeId.nodeId(nodeIdJson.asText()),
229 + DeviceId.deviceId(deviceIdJson.asText()), role), RESULT_NOT_FOUND);
230 + result.get();
231 +
232 + return Response.ok().build();
233 + } catch (InterruptedException | ExecutionException | IOException e) {
234 + throw new IllegalArgumentException(e);
235 + }
236 + }
237 +
238 + /**
239 + * Balances the mastership to be shared as evenly as possibly by all
240 + * online instances.
241 + *
242 + * @return status of the request - OK if the request is successfully processed
243 + */
244 + @GET
245 + @Produces(MediaType.APPLICATION_JSON)
246 + public Response balanceRoles() {
247 + mastershipAdminService.balanceRoles();
248 + return Response.ok().build();
249 + }
250 +}
1 +{
2 + "type": "object",
3 + "title": "mastership",
4 + "required": [
5 + "deviceId",
6 + "nodeId",
7 + "mastershipRole"
8 + ],
9 + "properties": {
10 + "deviceId": {
11 + "type": "string",
12 + "example": "of:0000000000000001"
13 + },
14 + "nodeId": {
15 + "type": "string",
16 + "example": "1"
17 + },
18 + "mastershipRole": {
19 + "type": "string",
20 + "example": "MASTER"
21 + }
22 + }
23 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "mastershipRole",
4 + "required": [
5 + "role"
6 + ],
7 + "properties": {
8 + "role": {
9 + "type": "string",
10 + "example": "MASTER"
11 + }
12 + }
13 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "roleInfo",
4 + "required": [
5 + "master",
6 + "backups"
7 + ],
8 + "properties": {
9 + "master": {
10 + "type": "string",
11 + "example": "1"
12 + },
13 + "backups": {
14 + "type": "array",
15 + "xml": {
16 + "name": "backups",
17 + "wrapped": true
18 + },
19 + "items": {
20 + "type": "string",
21 + "example": "1"
22 + }
23 + }
24 + }
25 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.rest.resources;
17 +
18 +import com.eclipsesource.json.Json;
19 +import com.eclipsesource.json.JsonArray;
20 +import com.eclipsesource.json.JsonObject;
21 +import com.google.common.collect.ImmutableList;
22 +import com.google.common.collect.ImmutableSet;
23 +import org.apache.commons.lang3.StringUtils;
24 +import org.hamcrest.Description;
25 +import org.hamcrest.TypeSafeMatcher;
26 +import org.junit.Before;
27 +import org.junit.Test;
28 +import org.onlab.osgi.ServiceDirectory;
29 +import org.onlab.osgi.TestServiceDirectory;
30 +import org.onlab.rest.BaseResource;
31 +import org.onosproject.cluster.NodeId;
32 +import org.onosproject.cluster.RoleInfo;
33 +import org.onosproject.codec.CodecService;
34 +import org.onosproject.codec.impl.CodecManager;
35 +import org.onosproject.mastership.MastershipAdminService;
36 +import org.onosproject.mastership.MastershipService;
37 +import org.onosproject.net.DeviceId;
38 +import org.onosproject.net.MastershipRole;
39 +
40 +import javax.ws.rs.client.WebTarget;
41 +import javax.ws.rs.core.Response;
42 +import java.net.HttpURLConnection;
43 +import java.util.List;
44 +import java.util.Set;
45 +
46 +import static org.easymock.EasyMock.anyObject;
47 +import static org.easymock.EasyMock.createMock;
48 +import static org.easymock.EasyMock.expect;
49 +import static org.easymock.EasyMock.expectLastCall;
50 +import static org.easymock.EasyMock.replay;
51 +import static org.hamcrest.Matchers.hasSize;
52 +import static org.hamcrest.Matchers.is;
53 +import static org.hamcrest.Matchers.notNullValue;
54 +import static org.junit.Assert.assertEquals;
55 +import static org.junit.Assert.assertThat;
56 +import static org.onosproject.net.MastershipRole.MASTER;
57 +
58 +/**
59 + * Unit tests for Mastership REST APIs.
60 + */
61 +public final class MastershipResourceTest extends ResourceTest {
62 +
63 + private final MastershipService mockService = createMock(MastershipService.class);
64 + private final MastershipAdminService mockAdminService =
65 + createMock(MastershipAdminService.class);
66 +
67 + private final DeviceId deviceId1 = DeviceId.deviceId("dev:1");
68 + private final DeviceId deviceId2 = DeviceId.deviceId("dev:2");
69 + private final DeviceId deviceId3 = DeviceId.deviceId("dev:3");
70 +
71 + private final NodeId nodeId1 = NodeId.nodeId("node:1");
72 + private final NodeId nodeId2 = NodeId.nodeId("node:2");
73 + private final NodeId nodeId3 = NodeId.nodeId("node:3");
74 + private final MastershipRole role1 = MASTER;
75 +
76 + /**
77 + * Creates a mock role info which is comprised of one master and three backups.
78 + *
79 + * @return a mock role info instance
80 + */
81 + private RoleInfo createMockRoleInfo() {
82 + NodeId master = NodeId.nodeId("master");
83 + List<NodeId> backups = ImmutableList.of(nodeId1, nodeId2, nodeId3);
84 +
85 + return new RoleInfo(master, backups);
86 + }
87 +
88 + private static final class RoleInfoJsonMatcher extends TypeSafeMatcher<JsonObject> {
89 + private final RoleInfo roleInfo;
90 + private String reason = "";
91 +
92 + private RoleInfoJsonMatcher(RoleInfo roleInfo) {
93 + this.roleInfo = roleInfo;
94 + }
95 +
96 + @Override
97 + protected boolean matchesSafely(JsonObject jsonNode) {
98 +
99 + // check master node identifier
100 + String jsonNodeId = jsonNode.get("master") != null ?
101 + jsonNode.get("master").asString() : null;
102 + String nodeId = roleInfo.master().id();
103 + if (!StringUtils.equals(jsonNodeId, nodeId)) {
104 + reason = "master's node id was " + jsonNodeId;
105 + return false;
106 + }
107 +
108 + // check backup nodes size
109 + final JsonArray jsonBackupNodeIds = jsonNode.get("backups").asArray();
110 + if (jsonBackupNodeIds.size() != roleInfo.backups().size()) {
111 + reason = "backup nodes size was " + jsonBackupNodeIds.size();
112 + return false;
113 + }
114 +
115 + // check backup nodes' identifier
116 + for (NodeId backupNodeId : roleInfo.backups()) {
117 + boolean backupFound = false;
118 + for (int idx = 0; idx < jsonBackupNodeIds.size(); idx++) {
119 + if (backupNodeId.id().equals(jsonBackupNodeIds.get(idx).asString())) {
120 + backupFound = true;
121 + break;
122 + }
123 + }
124 + if (!backupFound) {
125 + reason = "backup not found " + backupNodeId.id();
126 + return false;
127 + }
128 + }
129 +
130 + return true;
131 + }
132 +
133 + @Override
134 + public void describeTo(Description description) {
135 + description.appendText(reason);
136 + }
137 + }
138 +
139 + /**
140 + * Factory to allocate a role info json matcher.
141 + *
142 + * @param roleInfo role info object we are looking for
143 + * @return matcher
144 + */
145 + private static RoleInfoJsonMatcher matchesRoleInfo(RoleInfo roleInfo) {
146 + return new RoleInfoJsonMatcher(roleInfo);
147 + }
148 +
149 + /**
150 + * Sets up the global values for all the tests.
151 + */
152 + @Before
153 + public void setUpTest() {
154 +
155 + final CodecManager codecService = new CodecManager();
156 + codecService.activate();
157 + ServiceDirectory testDirectory =
158 + new TestServiceDirectory()
159 + .add(MastershipService.class, mockService)
160 + .add(MastershipAdminService.class, mockAdminService)
161 + .add(CodecService.class, codecService);
162 +
163 + BaseResource.setServiceDirectory(testDirectory);
164 + }
165 +
166 + /**
167 + * Tests the result of the REST API GET when there are active master roles.
168 + */
169 + @Test
170 + public void testGetLocalRole() {
171 + expect(mockService.getLocalRole(anyObject())).andReturn(role1).anyTimes();
172 + replay(mockService);
173 +
174 + final WebTarget wt = target();
175 + final String response = wt.path("mastership/" + deviceId1.toString() +
176 + "/local").request().get(String.class);
177 + final JsonObject result = Json.parse(response).asObject();
178 + assertThat(result, notNullValue());
179 +
180 + assertThat(result.names(), hasSize(1));
181 + assertThat(result.names().get(0), is("role"));
182 +
183 + final String role = result.get("role").asString();
184 + assertThat(role, notNullValue());
185 + assertThat(role, is("MASTER"));
186 + }
187 +
188 + /**
189 + * Tests the result of the REST API GET when there is no active master.
190 + */
191 + @Test
192 + public void testGetMasterForNull() {
193 + expect(mockService.getMasterFor(anyObject())).andReturn(null).anyTimes();
194 + replay(mockService);
195 +
196 + final WebTarget wt = target();
197 + final Response response = wt.path("mastership/" + deviceId1.toString() +
198 + "/master").request().get();
199 + assertEquals(404, response.getStatus());
200 + }
201 +
202 + /**
203 + * Tests the result of the REST API GET when there is active master.
204 + */
205 + @Test
206 + public void testGetMasterFor() {
207 + expect(mockService.getMasterFor(anyObject())).andReturn(nodeId1).anyTimes();
208 + replay(mockService);
209 +
210 + final WebTarget wt = target();
211 + final String response = wt.path("mastership/" + deviceId1.toString() +
212 + "/master").request().get(String.class);
213 + final JsonObject result = Json.parse(response).asObject();
214 + assertThat(result, notNullValue());
215 +
216 + assertThat(result.names(), hasSize(1));
217 + assertThat(result.names().get(0), is("node"));
218 +
219 + final String node = result.get("node").asString();
220 + assertThat(node, notNullValue());
221 + assertThat(node, is("node:1"));
222 + }
223 +
224 + /**
225 + * Tests the result of the REST API GET when there are no active nodes.
226 + */
227 + @Test
228 + public void testGetNodesForNull() {
229 + expect(mockService.getNodesFor(anyObject())).andReturn(null).anyTimes();
230 + replay(mockService);
231 +
232 + final WebTarget wt = target();
233 + final Response response = wt.path("mastership/" + deviceId1.toString() +
234 + "/role").request().get();
235 + assertEquals(404, response.getStatus());
236 + }
237 +
238 + /**
239 + * Tests the result of the REST API GET when there are active nodes.
240 + */
241 + @Test
242 + public void testGetNodesFor() {
243 + RoleInfo mockRoleInfo = createMockRoleInfo();
244 + expect(mockService.getNodesFor(anyObject())).andReturn(mockRoleInfo).anyTimes();
245 + replay(mockService);
246 +
247 + final WebTarget wt = target();
248 + final String response = wt.path("mastership/" + deviceId1.toString() +
249 + "/role").request().get(String.class);
250 + final JsonObject result = Json.parse(response).asObject();
251 + assertThat(result, notNullValue());
252 +
253 + assertThat(result, matchesRoleInfo(mockRoleInfo));
254 + }
255 +
256 + /**
257 + * Tests the result of the REST API GET when there are active devices.
258 + */
259 + @Test
260 + public void testGetDevicesOf() {
261 + Set<DeviceId> deviceIds = ImmutableSet.of(deviceId1, deviceId2, deviceId3);
262 + expect(mockService.getDevicesOf(anyObject())).andReturn(deviceIds).anyTimes();
263 + replay(mockService);
264 +
265 + final WebTarget wt = target();
266 + final String response = wt.path("mastership/" + deviceId1.toString() +
267 + "/device").request().get(String.class);
268 + final JsonObject result = Json.parse(response).asObject();
269 + assertThat(result, notNullValue());
270 +
271 + assertThat(result.names(), hasSize(1));
272 + assertThat(result.names().get(0), is("devices"));
273 +
274 + final JsonArray jsonDevices = result.get("devices").asArray();
275 + assertThat(jsonDevices, notNullValue());
276 + assertThat(jsonDevices.size(), is(3));
277 + }
278 +
279 + /**
280 + * Tests the result of the REST API GET for requesting mastership role.
281 + */
282 + @Test
283 + public void testRequestRoleFor() {
284 + // TODO: will be added when CompletableFuture is removed
285 + }
286 +
287 + /**
288 + * Tests the result of the REST API GET for relinquishing mastership role.
289 + */
290 + @Test
291 + public void testRelinquishMastership() {
292 + // TODO: will be added when CompletableFuture is removed
293 + }
294 +
295 + /**
296 + * Tests the result of the REST API PUT for setting role.
297 + */
298 + @Test
299 + public void testSetRole() {
300 + // TODO: will be added when CompletableFuture is removed
301 + }
302 +
303 + /**
304 + * Tests the result of the REST API GET for balancing roles.
305 + */
306 + @Test
307 + public void testBalanceRoles() {
308 + mockAdminService.balanceRoles();
309 + expectLastCall();
310 + replay(mockAdminService);
311 +
312 + final WebTarget wt = target();
313 + final Response response = wt.path("mastership").request().get();
314 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
315 + }
316 +}
1 +{
2 + "deviceId": "of:0000000000000001",
3 + "nodeId": "1",
4 + "role": "MASTER"
5 +}
...\ No newline at end of file ...\ No newline at end of file