-
Notifications
You must be signed in to change notification settings - Fork 3.4k
HBASE-26192 Master UI hbck should provide a JSON formatted output option #4470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.hadoop.hbase.master.http; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.PrintWriter; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import javax.servlet.ServletException; | ||
| import javax.servlet.http.HttpServlet; | ||
| import javax.servlet.http.HttpServletRequest; | ||
| import javax.servlet.http.HttpServletResponse; | ||
| import org.apache.hadoop.fs.Path; | ||
| import org.apache.hadoop.hbase.ServerName; | ||
| import org.apache.hadoop.hbase.client.RegionInfo; | ||
| import org.apache.hadoop.hbase.master.HMaster; | ||
| import org.apache.hadoop.hbase.master.HbckChore; | ||
| import org.apache.hadoop.hbase.master.http.gson.GsonFactory; | ||
| import org.apache.hadoop.hbase.master.janitor.CatalogJanitor; | ||
| import org.apache.hadoop.hbase.master.janitor.Report; | ||
| import org.apache.hadoop.hbase.util.Pair; | ||
| import org.apache.yetus.audience.InterfaceAudience; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import org.apache.hbase.thirdparty.com.google.gson.Gson; | ||
|
|
||
| @InterfaceAudience.Private | ||
| public class MasterHbckServlet extends HttpServlet { | ||
|
|
||
| public static final String START_TIMESTAMP = "start_timestamp"; | ||
| public static final String END_TIMESTAMP = "end_timestamp"; | ||
| public static final String INCONSISTENT_REGIONS = "inconsistent_regions"; | ||
| public static final String ORPHAN_REGIONS_ON_RS = "orphan_regions_on_rs"; | ||
| public static final String ORPHAN_REGIONS_ON_FS = "orphan_regions_on_fs"; | ||
| public static final String HOLES = "holes"; | ||
| public static final String OVERLAPS = "overlaps"; | ||
| public static final String UNKNOWN_SERVERS = "unknown_servers"; | ||
| public static final String EMPTY_REGIONINFO = "empty_regioninfo"; | ||
|
|
||
| private static final long serialVersionUID = 1L; | ||
| private static final Logger LOG = LoggerFactory.getLogger(MasterHbckServlet.class); | ||
| private static final Gson GSON = GsonFactory.buildGson(); | ||
|
|
||
| @Override | ||
| public void doGet(HttpServletRequest request, HttpServletResponse response) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why use the raw Servlet API when we have Jersey? Instead of building a response by hand, you could instantiate a POJO and return it, be done.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other servlets are implemented using the Servlet API and I used them as template. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree to Nick’s comment on the REST Endpoint returning JSON backed my Model which is a standard way of implementing REST APIs. |
||
| throws ServletException, IOException { | ||
| final HMaster master = (HMaster) getServletContext().getAttribute(HMaster.MASTER); | ||
| if (!master.isInitialized()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, this is not something I thought about. Seems reasonable to handle it with a redirect. |
||
| LOG.warn("Master is not initialized yet"); | ||
| sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, | ||
| "master is not initialized yet"); | ||
| return; | ||
| } | ||
| final HbckChore hbckChore = master.getHbckChore(); | ||
| if (hbckChore == null || hbckChore.isDisabled()) { | ||
| LOG.warn("Hbck chore is disabled"); | ||
| sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Hbck chore is disabled"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really a 503? The server is fine. I'm no HTTP status code expert, but I'd say this is more like a 204
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the HBCK chore is not available the service is not available, was my thinking. However I can see 204 being quite appropriate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 404 is usually for resource not found right - if hbck is the resource which is disabled, it means the requested resource is not found. |
||
| return; | ||
| } | ||
| if (!Boolean.parseBoolean(request.getParameter("cache"))) { | ||
| try { | ||
| master.getMasterRpcServices().runHbckChore(null, null); | ||
| } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException se) { | ||
| LOG.warn("Failed generating a new hbck chore report; using cache", se); | ||
| } | ||
| try { | ||
| master.getMasterRpcServices().runCatalogScan(null, null); | ||
| } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException se) { | ||
| LOG.warn("Failed generating a new catalogjanitor report; using cache", se); | ||
| } | ||
| } | ||
| Map<String, Object> result = new HashMap<>(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you define a POJO for the response object, we can have some hope of tracing API compatibility in the future using our existing compatibility report.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, this is a good reason. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With POJO, we can achieve something like what OData does to even query for a sub document as mentioned here. I am not sure if there is a right equivalent support already available in HBase, else we can skip the OData part but the POJO adds value. |
||
| result.put(START_TIMESTAMP, hbckChore.getCheckingStartTimestamp()); | ||
| result.put(END_TIMESTAMP, hbckChore.getCheckingEndTimestamp()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our HBCK chore should expose a report so that a reader can get a static view... or a read-write lock so that a reader can block a writer from clobbering its view. |
||
| final Map<String, Pair<ServerName, List<ServerName>>> inconsistentRegions = | ||
| hbckChore.getInconsistentRegions(); | ||
| if (inconsistentRegions != null && !inconsistentRegions.isEmpty()) { | ||
| result.put(INCONSISTENT_REGIONS, inconsistentRegions); | ||
| } | ||
| final Map<String, ServerName> orphanRegionsOnRS = hbckChore.getOrphanRegionsOnRS(); | ||
| if (orphanRegionsOnRS != null && !orphanRegionsOnRS.isEmpty()) { | ||
| result.put(ORPHAN_REGIONS_ON_RS, orphanRegionsOnRS); | ||
| } | ||
| final Map<String, Path> orphanRegionsOnFS = hbckChore.getOrphanRegionsOnFS(); | ||
| if (orphanRegionsOnFS != null && !orphanRegionsOnFS.isEmpty()) { | ||
| result.put(ORPHAN_REGIONS_ON_FS, orphanRegionsOnFS); | ||
| } | ||
| final CatalogJanitor janitor = master.getCatalogJanitor(); | ||
| if (janitor != null) { | ||
| final Report report = janitor.getLastReport(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CatalogueJanitor exposes the report concept. Brilliant!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and unfortunately the result is a mix of CatalogJanitor's nicely structured Report and HbckChore's other return types. Unifying these things is out of scope, though. Or, we can make it a precondition, but I won't pursue it at this time.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Down review I accepted your proposal to implement this with Jersey, so a clean unification of these two data sources can be done when working up the model (POJO) of the report encoding to return from the request handler. |
||
| if (report != null && !report.isEmpty()) { | ||
| List<Pair<RegionInfo, RegionInfo>> holes = report.getHoles(); | ||
| if (holes != null && !holes.isEmpty()) { | ||
| result.put(HOLES, holes); | ||
| } | ||
| List<Pair<RegionInfo, RegionInfo>> overlaps = report.getOverlaps(); | ||
| if (overlaps != null && !overlaps.isEmpty()) { | ||
| result.put(OVERLAPS, overlaps); | ||
| } | ||
| List<Pair<RegionInfo, ServerName>> unknownServers = report.getUnknownServers(); | ||
| if (unknownServers != null && !unknownServers.isEmpty()) { | ||
| result.put(UNKNOWN_SERVERS, unknownServers); | ||
| } | ||
| List<byte[]> emptyRegionInfo = report.getEmptyRegionInfo(); | ||
| if (!emptyRegionInfo.isEmpty()) { | ||
| result.put(EMPTY_REGIONINFO, emptyRegionInfo); | ||
| } | ||
| } | ||
| } | ||
| response.setContentType("application/json"); | ||
| PrintWriter out = response.getWriter(); | ||
| out.write(GSON.toJson(result)); | ||
| out.write('\n'); | ||
| } | ||
|
|
||
| private static void sendError(HttpServletResponse response, int code, String message) | ||
| throws IOException { | ||
| response.setContentType("application/json"); | ||
| Map<String, Object> result = new HashMap<>(); | ||
| result.put("error", message); | ||
| response.setStatus(code); | ||
| PrintWriter out = response.getWriter(); | ||
| out.write(GSON.toJson(result)); | ||
| out.write('\n'); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.hadoop.hbase.master.http; | ||
|
|
||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertNotNull; | ||
| import static org.junit.Assert.assertNull; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.ArgumentMatchers.anyString; | ||
| import static org.mockito.Mockito.doAnswer; | ||
| import static org.mockito.Mockito.doReturn; | ||
| import static org.mockito.Mockito.mock; | ||
|
|
||
| import java.io.ByteArrayOutputStream; | ||
| import java.io.PrintWriter; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import javax.servlet.ServletConfig; | ||
| import javax.servlet.ServletContext; | ||
| import javax.servlet.http.HttpServletRequest; | ||
| import javax.servlet.http.HttpServletResponse; | ||
| import org.apache.hadoop.fs.Path; | ||
| import org.apache.hadoop.hbase.HBaseClassTestRule; | ||
| import org.apache.hadoop.hbase.HBaseConfiguration; | ||
| import org.apache.hadoop.hbase.ServerName; | ||
| import org.apache.hadoop.hbase.TableName; | ||
| import org.apache.hadoop.hbase.client.RegionInfo; | ||
| import org.apache.hadoop.hbase.client.RegionInfoBuilder; | ||
| import org.apache.hadoop.hbase.client.TableDescriptor; | ||
| import org.apache.hadoop.hbase.client.TableDescriptorBuilder; | ||
| import org.apache.hadoop.hbase.master.HMaster; | ||
| import org.apache.hadoop.hbase.master.HbckChore; | ||
| import org.apache.hadoop.hbase.master.MasterRpcServices; | ||
| import org.apache.hadoop.hbase.master.http.gson.GsonFactory; | ||
| import org.apache.hadoop.hbase.master.janitor.CatalogJanitor; | ||
| import org.apache.hadoop.hbase.master.janitor.Report; | ||
| import org.apache.hadoop.hbase.testclassification.MasterTests; | ||
| import org.apache.hadoop.hbase.testclassification.MediumTests; | ||
| import org.apache.hadoop.hbase.util.Bytes; | ||
| import org.apache.hadoop.hbase.util.Pair; | ||
| import org.junit.Before; | ||
| import org.junit.ClassRule; | ||
| import org.junit.Test; | ||
| import org.junit.experimental.categories.Category; | ||
| import org.mockito.invocation.InvocationOnMock; | ||
| import org.mockito.stubbing.Answer; | ||
|
|
||
| import org.apache.hbase.thirdparty.com.google.gson.Gson; | ||
| import org.apache.hbase.thirdparty.com.google.gson.reflect.TypeToken; | ||
|
|
||
| /** | ||
| * Tests for the master hbck servlet. | ||
| */ | ||
| @Category({ MasterTests.class, MediumTests.class }) | ||
| public class TestMasterHbckServlet { | ||
|
|
||
| @ClassRule | ||
| public static final HBaseClassTestRule CLASS_RULE = | ||
| HBaseClassTestRule.forClass(TestMasterHbckServlet.class); | ||
|
|
||
| static final ServerName FAKE_HOST = ServerName.valueOf("fakehost", 12345, 1234567890); | ||
| static final ServerName FAKE_HOST_2 = ServerName.valueOf("fakehost2", 12345, 1234567890); | ||
| static final TableDescriptor FAKE_TABLE = | ||
| TableDescriptorBuilder.newBuilder(TableName.valueOf("mytable")).build(); | ||
| static final RegionInfo FAKE_HRI = RegionInfoBuilder.newBuilder(FAKE_TABLE.getTableName()) | ||
| .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("b")).build(); | ||
| static final RegionInfo FAKE_HRI_2 = RegionInfoBuilder.newBuilder(FAKE_TABLE.getTableName()) | ||
| .setStartKey(Bytes.toBytes("a")).setEndKey(Bytes.toBytes("c")).build(); | ||
| static final RegionInfo FAKE_HRI_3 = RegionInfoBuilder.newBuilder(FAKE_TABLE.getTableName()) | ||
| .setStartKey(Bytes.toBytes("d")).setEndKey(Bytes.toBytes("e")).build(); | ||
| static final Path FAKE_PATH = new Path( | ||
| "/hbase/data/default/" + FAKE_TABLE.getTableName() + "/" + FAKE_HRI_3.getEncodedName()); | ||
| static final long FAKE_START_TIMESTAMP = System.currentTimeMillis(); | ||
| static final long FAKE_END_TIMESTAMP = System.currentTimeMillis() + 1000; | ||
| static final Gson GSON = GsonFactory.buildGson(); | ||
|
|
||
| private HMaster master; | ||
|
|
||
| @Before | ||
| public void setupMocks() { | ||
| // Fake inconsistentRegions | ||
| Map<String, Pair<ServerName, List<ServerName>>> inconsistentRegions = new HashMap<>(); | ||
| inconsistentRegions.put(FAKE_HRI.getEncodedName(), | ||
| new Pair<>(FAKE_HOST, Arrays.asList(FAKE_HOST_2))); | ||
|
|
||
| // Fake orphanRegionsOnRS | ||
| Map<String, ServerName> orphanRegionsOnRS = new HashMap<>(); | ||
|
|
||
| // Fake orphanRegionsOnFS | ||
| Map<String, Path> orphanRegionsOnFS = new HashMap<>(); | ||
| orphanRegionsOnFS.put(FAKE_HRI_3.getEncodedName(), FAKE_PATH); | ||
|
|
||
| // Fake HbckChore | ||
| HbckChore hbckChore = mock(HbckChore.class); | ||
| doReturn(FAKE_START_TIMESTAMP).when(hbckChore).getCheckingStartTimestamp(); | ||
| doReturn(FAKE_END_TIMESTAMP).when(hbckChore).getCheckingEndTimestamp(); | ||
| doReturn(inconsistentRegions).when(hbckChore).getInconsistentRegions(); | ||
| doReturn(orphanRegionsOnRS).when(hbckChore).getOrphanRegionsOnRS(); | ||
| doReturn(orphanRegionsOnFS).when(hbckChore).getOrphanRegionsOnFS(); | ||
|
|
||
| // Fake region holes | ||
| List<Pair<RegionInfo, RegionInfo>> holes = new ArrayList<>(); | ||
|
|
||
| // Fake region overlaps | ||
| List<Pair<RegionInfo, RegionInfo>> overlaps = new ArrayList<>(); | ||
| overlaps.add(new Pair<>(FAKE_HRI, FAKE_HRI_2)); | ||
|
|
||
| // Fake unknown servers | ||
| List<Pair<RegionInfo, ServerName>> unknownServers = new ArrayList<>(); | ||
| unknownServers.add(new Pair<>(FAKE_HRI, FAKE_HOST_2)); | ||
|
|
||
| // Fake empty region info | ||
| List<String> emptyRegionInfo = new ArrayList<>(); | ||
| emptyRegionInfo.add(FAKE_HRI_3.getEncodedName()); | ||
|
|
||
| // Fake catalog janitor report | ||
| Report report = mock(Report.class); | ||
| doReturn(FAKE_START_TIMESTAMP).when(report).getCreateTime(); | ||
| doReturn(holes).when(report).getHoles(); | ||
| doReturn(overlaps).when(report).getOverlaps(); | ||
| doReturn(unknownServers).when(report).getUnknownServers(); | ||
| doReturn(emptyRegionInfo).when(report).getEmptyRegionInfo(); | ||
|
|
||
| // Fake CatalogJanitor | ||
| CatalogJanitor janitor = mock(CatalogJanitor.class); | ||
| doReturn(report).when(janitor).getLastReport(); | ||
|
|
||
| // Fake master | ||
|
|
||
| master = mock(HMaster.class); | ||
| doReturn(HBaseConfiguration.create()).when(master).getConfiguration(); | ||
| doReturn(true).when(master).isInitialized(); | ||
| doReturn(Optional.of(FAKE_HOST)).when(master).getActiveMaster(); | ||
| doReturn(janitor).when(master).getCatalogJanitor(); | ||
| doReturn(hbckChore).when(master).getHbckChore(); | ||
| MasterRpcServices services = mock(MasterRpcServices.class); | ||
| doReturn(services).when(master).getRpcServices(); | ||
| doReturn(services).when(master).getMasterRpcServices(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do all this mockery? At this point, it's it simpler to stand up a mini-cluster and manufacture the state you need? It looks like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because when I wrote this test this was the way I understood it could be implemented and I don't think too much is mocked here, just what is needed to produce a report that has exactly what I want to check for later. |
||
| } | ||
|
|
||
| @SuppressWarnings({ "unchecked", "rawtypes" }) | ||
| @Test | ||
| public void testHbckServletWithMocks() throws Exception { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No testing of error states? |
||
| // Set up request and response mocks | ||
| HttpServletRequest request = mock(HttpServletRequest.class); | ||
| HttpServletResponse response = mock(HttpServletResponse.class); | ||
| ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||
| PrintWriter writer = new PrintWriter(out); | ||
| doReturn(writer).when(response).getWriter(); | ||
|
|
||
| // Set up servlet config | ||
| ServletContext context = mock(ServletContext.class); | ||
| Map<String, Object> configMap = new HashMap<>(); | ||
| doAnswer(new Answer<Object>() { | ||
| @Override | ||
| public Object answer(InvocationOnMock invocation) throws Throwable { | ||
| return configMap.get(invocation.getArgument(0)); | ||
| } | ||
| }).when(context).getAttribute(anyString()); | ||
| doAnswer(new Answer<Void>() { | ||
| @Override | ||
| public Void answer(InvocationOnMock invocation) throws Throwable { | ||
| configMap.put(invocation.getArgument(0), invocation.getArgument(1)); | ||
| return null; | ||
| } | ||
| }).when(context).setAttribute(anyString(), any()); | ||
| ServletConfig config = mock(ServletConfig.class); | ||
| doReturn(context).when(config).getServletContext(); | ||
|
|
||
| // Instantiate the servlet and call doGet | ||
| MasterHbckServlet servlet = new MasterHbckServlet(); | ||
| servlet.init(config); | ||
| servlet.getServletContext().setAttribute(HMaster.MASTER, master); | ||
| servlet.doGet(request, response); | ||
| // Flush the writer | ||
| writer.flush(); | ||
|
|
||
| // Response should be now be in 'out' | ||
| String responseString = new String(out.toByteArray(), StandardCharsets.UTF_8); | ||
| Map<String, Object> result = | ||
| GSON.fromJson(responseString, new TypeToken<Map<String, Object>>() { | ||
| }.getType()); | ||
|
|
||
| // Check that the result is as expected | ||
| long startTimestamp = ((Double) result.get(MasterHbckServlet.START_TIMESTAMP)).longValue(); | ||
| assertEquals(FAKE_START_TIMESTAMP, startTimestamp); | ||
| long endTimestamp = ((Double) result.get(MasterHbckServlet.END_TIMESTAMP)).longValue(); | ||
| assertEquals(FAKE_END_TIMESTAMP, endTimestamp); | ||
| Map<String, Object> inconsistentRegions = | ||
| (Map<String, Object>) result.get(MasterHbckServlet.INCONSISTENT_REGIONS); | ||
| assertNotNull(inconsistentRegions); | ||
| assertEquals(1, inconsistentRegions.size()); | ||
| assertNotNull(inconsistentRegions.get(FAKE_HRI.getEncodedName())); | ||
| assertNull(inconsistentRegions.get(FAKE_HRI_3.getEncodedName())); | ||
| Map<String, Object> orphanRegionsOnRS = | ||
| (Map<String, Object>) result.get(MasterHbckServlet.ORPHAN_REGIONS_ON_RS); | ||
| assertNull(orphanRegionsOnRS); | ||
| Map<String, String> orphanRegionsOnFS = | ||
| (Map<String, String>) result.get(MasterHbckServlet.ORPHAN_REGIONS_ON_FS); | ||
| assertNotNull(orphanRegionsOnFS); | ||
| assertEquals(1, orphanRegionsOnFS.size()); | ||
| assertNull(orphanRegionsOnFS.get(FAKE_HRI.getEncodedName())); | ||
| assertNotNull(orphanRegionsOnFS.get(FAKE_HRI_3.getEncodedName())); | ||
| List holes = (List) result.get(MasterHbckServlet.HOLES); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that we are just comparing null vs non-null and size of the list, this raw use of |
||
| assertNull(holes); | ||
| List overlaps = (List) result.get(MasterHbckServlet.OVERLAPS); | ||
| assertNotNull(overlaps); | ||
| assertEquals(1, overlaps.size()); | ||
| List unknownServers = (List) result.get(MasterHbckServlet.UNKNOWN_SERVERS); | ||
| assertNotNull(unknownServers); | ||
| assertEquals(1, unknownServers.size()); | ||
| List emptyRegionInfo = (List) result.get(MasterHbckServlet.EMPTY_REGIONINFO); | ||
| assertNotNull(emptyRegionInfo); | ||
| assertEquals(1, emptyRegionInfo.size()); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add a new endpoint instead of having the existing
/hbck.jsphonor an "application/json" header and render its response there? Probably a bunch of that jsp's POJOs can be reused...Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not and still do not know how to make a JSP page honor different content-encodings. JSP is not the tool I would like to implement this with. If this is the preferred implementation that's fine but I would not want to pursue it.