-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[improve] [admin] [PIP-179] Support the admin API to check unknown re…
…quest parameters (#16577)
- Loading branch information
1 parent
15a0013
commit 93e947b
Showing
7 changed files
with
312 additions
and
13 deletions.
There are no files selected for viewing
56 changes: 56 additions & 0 deletions
56
...-common/src/main/java/org/apache/pulsar/broker/web/DynamicSkipUnknownPropertyHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* 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.pulsar.broker.web; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JsonDeserializer; | ||
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; | ||
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; | ||
import java.io.IOException; | ||
import java.util.Collection; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
public class DynamicSkipUnknownPropertyHandler extends DeserializationProblemHandler { | ||
|
||
@Getter | ||
@Setter | ||
private boolean skipUnknownProperty = true; | ||
|
||
@Override | ||
public boolean handleUnknownProperty(DeserializationContext deserializationContext, JsonParser p, | ||
JsonDeserializer<?> deserializer, Object beanOrClass, | ||
String propertyName) throws IOException { | ||
Collection<Object> propIds = (deserializer == null) ? null : deserializer.getKnownPropertyNames(); | ||
UnrecognizedPropertyException unrecognizedPropertyException = UnrecognizedPropertyException | ||
.from(p, beanOrClass, propertyName, propIds); | ||
if (skipUnknownProperty){ | ||
if (log.isDebugEnabled()) { | ||
log.debug(unrecognizedPropertyException.getMessage()); | ||
} | ||
p.skipChildren(); | ||
return skipUnknownProperty; | ||
} else { | ||
throw unrecognizedPropertyException; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
...mon/src/test/java/org/apache/pulsar/broker/web/DynamicSkipUnknownPropertyHandlerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/** | ||
* 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.pulsar.broker.web; | ||
|
||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.ObjectReader; | ||
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; | ||
import lombok.Data; | ||
import org.testng.Assert; | ||
import org.testng.annotations.Test; | ||
|
||
@Test(groups = "broker-admin") | ||
public class DynamicSkipUnknownPropertyHandlerTest { | ||
|
||
@Test | ||
public void testHandleUnknownProperty() throws Exception{ | ||
DynamicSkipUnknownPropertyHandler handler = new DynamicSkipUnknownPropertyHandler(); | ||
handler.setSkipUnknownProperty(true); | ||
// Case 1: initial ObjectMapper with "enable feature". | ||
ObjectMapper objectMapper = new ObjectMapper(); | ||
objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); | ||
objectMapper.addHandler(handler); | ||
ObjectReader objectReader = objectMapper.readerFor(TestBean.class); | ||
// Assert skip unknown property and logging: objectMapper. | ||
String json = "{\"name1\": \"James\",\"nm\":\"Paul\",\"name2\":\"Eric\"}"; | ||
TestBean testBean = objectMapper.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
// Assert skip unknown property and logging: objectReader. | ||
testBean = objectReader.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
// Assert failure on unknown property. | ||
handler.setSkipUnknownProperty(false); | ||
try { | ||
objectMapper.readValue(json, TestBean.class); | ||
Assert.fail("Expect UnrecognizedPropertyException when set skipUnknownProperty false."); | ||
} catch (UnrecognizedPropertyException e){ | ||
|
||
} | ||
try { | ||
objectReader.readValue(json, TestBean.class); | ||
Assert.fail("Expect UnrecognizedPropertyException when set skipUnknownProperty false."); | ||
} catch (UnrecognizedPropertyException e){ | ||
|
||
} | ||
// Case 2: initial ObjectMapper with "disabled feature". | ||
objectMapper = new ObjectMapper(); | ||
objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); | ||
objectMapper.addHandler(handler); | ||
objectReader = objectMapper.readerFor(TestBean.class); | ||
// Assert failure on unknown property. | ||
try { | ||
objectMapper.readValue(json, TestBean.class); | ||
Assert.fail("Expect UnrecognizedPropertyException when set skipUnknownProperty false."); | ||
} catch (UnrecognizedPropertyException e){ | ||
|
||
} | ||
try { | ||
objectReader.readValue(json, TestBean.class); | ||
Assert.fail("Expect UnrecognizedPropertyException when set skipUnknownProperty false."); | ||
} catch (UnrecognizedPropertyException e){ | ||
|
||
} | ||
// Assert skip unknown property and logging. | ||
handler.setSkipUnknownProperty(true); | ||
testBean = objectMapper.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
testBean = objectReader.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
// Case 3: unknown property deserialize by object json. | ||
json = "{\"name1\": \"James\",\"nm\":{\"name\":\"Paul\",\"age\":18},\"name2\":\"Eric\"}"; | ||
// Assert skip unknown property and logging. | ||
handler.setSkipUnknownProperty(true); | ||
testBean = objectMapper.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
testBean = objectReader.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
// Case 4: unknown property deserialize by array json. | ||
json = "{\"name1\": \"James\",\"nm\":[\"name\",\"Paul\"],\"name2\":\"Eric\"}"; | ||
// Assert skip unknown property and logging. | ||
handler.setSkipUnknownProperty(true); | ||
testBean = objectMapper.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
testBean = objectReader.readValue(json, TestBean.class); | ||
Assert.assertNull(testBean.getName()); | ||
Assert.assertEquals(testBean.getName1(), "James"); | ||
Assert.assertEquals(testBean.getName2(), "Eric"); | ||
} | ||
|
||
@Data | ||
private static class TestBean { | ||
private String name1; | ||
private String name; | ||
private String name2; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/AdminRestTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* 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.pulsar.broker.admin; | ||
|
||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import javax.ws.rs.client.Client; | ||
import javax.ws.rs.client.ClientBuilder; | ||
import javax.ws.rs.client.Entity; | ||
import javax.ws.rs.client.WebTarget; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.pulsar.broker.auth.MockedPulsarServiceBaseTest; | ||
import org.apache.pulsar.common.policies.data.ClusterData; | ||
import org.apache.pulsar.common.policies.data.TenantInfoImpl; | ||
import org.testng.Assert; | ||
import org.testng.annotations.AfterMethod; | ||
import org.testng.annotations.BeforeMethod; | ||
import org.testng.annotations.Test; | ||
|
||
@Slf4j | ||
@Test(groups = "broker-admin") | ||
public class AdminRestTest extends MockedPulsarServiceBaseTest { | ||
|
||
private final String clusterName = "test"; | ||
private final String tenantName = "t-tenant"; | ||
private final String namespaceName = "t-tenant/test-namespace"; | ||
private final String topicNameSuffix = "t-rest-topic"; | ||
private final String topicName = "persistent://" + namespaceName + "/" + topicNameSuffix; | ||
|
||
@Test | ||
public void testRejectUnknownEntityProperties() throws Exception{ | ||
// Build request command. | ||
int port = pulsar.getWebService().getListenPortHTTP().get(); | ||
Client client = ClientBuilder.newClient(); | ||
WebTarget target = client.target("http://127.0.0.1:" + port | ||
+ "/admin/v2/persistent/" + namespaceName + "/" + topicNameSuffix + "/retention"); | ||
Map<String,Object> data = new HashMap<>(); | ||
data.put("retention_size_in_mb", -1); | ||
data.put("retention_time_in_minutes", 40320); | ||
// Configuration default, response success. | ||
Response response = target.request(MediaType.APPLICATION_JSON_TYPE).buildPost(Entity.json(data)).invoke(); | ||
Assert.assertTrue(response.getStatus() / 200 == 1); | ||
// Enabled feature, bad request response. | ||
pulsar.getWebService().getSharedUnknownPropertyHandler().setSkipUnknownProperty(false); | ||
response = target.request(MediaType.APPLICATION_JSON_TYPE).buildPost(Entity.json(data)).invoke(); | ||
Assert.assertEquals(response.getStatus(), 400); | ||
// Disabled feature, response success. | ||
pulsar.getWebService().getSharedUnknownPropertyHandler().setSkipUnknownProperty(true); | ||
response = target.request(MediaType.APPLICATION_JSON_TYPE).buildPost(Entity.json(data)).invoke(); | ||
Assert.assertTrue(response.getStatus() / 200 == 1); | ||
} | ||
|
||
@BeforeMethod | ||
@Override | ||
protected void setup() throws Exception { | ||
resetConfig(); | ||
super.internalSetup(); | ||
// Create tenant, namespace, topic | ||
admin.clusters().createCluster(clusterName, ClusterData.builder().serviceUrl(brokerUrl.toString()).build()); | ||
admin.tenants().createTenant(tenantName, | ||
new TenantInfoImpl(Collections.singleton("a"), Collections.singleton(clusterName))); | ||
admin.namespaces().createNamespace(namespaceName); | ||
admin.topics().createNonPartitionedTopic(topicName); | ||
} | ||
|
||
@AfterMethod | ||
@Override | ||
protected void cleanup() throws Exception { | ||
// cleanup. | ||
admin.topics().delete(topicName); | ||
admin.namespaces().deleteNamespace(namespaceName); | ||
admin.tenants().deleteTenant(tenantName); | ||
admin.clusters().deleteCluster(clusterName); | ||
// super cleanup. | ||
super.internalCleanup(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters