KNOX-1410 - Knox Shell support for remote Alias management (#210)
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/AbstractAliasRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/AbstractAliasRequest.java
new file mode 100644
index 0000000..39da1bd
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/AbstractAliasRequest.java
@@ -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.knox.gateway.shell.alias;
+
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.knox.gateway.shell.AbstractRequest;
+import org.apache.knox.gateway.shell.ErrorResponse;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.apache.knox.gateway.shell.KnoxShellException;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+public abstract class AbstractAliasRequest extends AbstractRequest<AliasResponse> {
+
+ public static final String SERVICE_PATH = "/admin/api/v1/aliases";
+
+ protected static final String GATEWAY_CLUSTER_NAME = "__gateway";
+
+ protected enum RequestType { GET, PUT, POST, DELETE }
+
+ private HttpRequestBase httpRequest;
+
+ protected URI requestURI;
+
+ protected String clusterName;
+
+ protected abstract RequestType getRequestType();
+
+ AbstractAliasRequest(final KnoxSession session) {
+ this(session, null, null);
+ }
+
+ AbstractAliasRequest(final KnoxSession session, final String clusterName) {
+ this(session, clusterName, null);
+ }
+
+ AbstractAliasRequest(final KnoxSession session, final String clusterName, final String doAsUser) {
+ super(session, doAsUser);
+ this.clusterName = clusterName != null ? clusterName : GATEWAY_CLUSTER_NAME;
+ }
+
+
+ public URI getRequestURI() {
+ return requestURI;
+ }
+
+ public HttpRequestBase getRequest() {
+ return httpRequest;
+ }
+
+ @Override
+ protected Callable<AliasResponse> callable() {
+ return () -> {
+ httpRequest = createRequest();
+ try {
+ return new AliasResponse(execute(httpRequest));
+ } catch (ErrorResponse e) {
+ return new AliasResponse(e.getResponse());
+ }
+ };
+ }
+
+ protected URI buildURI() {
+ try {
+ URIBuilder uri = uri(getPathElements().toArray(new String[]{}));
+ return uri.build();
+ } catch (URISyntaxException e) {
+ throw new KnoxShellException(e);
+ }
+ }
+
+ protected List<String> getPathElements() {
+ List<String> elements = new ArrayList<>();
+ elements.add(SERVICE_PATH);
+ if (clusterName != null) {
+ elements.add("/");
+ elements.add(clusterName);
+ }
+ return elements;
+ }
+
+ protected HttpRequestBase createRequest() {
+ HttpRequestBase request;
+
+ switch (getRequestType()) {
+ case POST:
+ request = new HttpPost(requestURI);
+ break;
+ case PUT:
+ request = new HttpPut(requestURI);
+ break;
+ case DELETE:
+ request = new HttpDelete(requestURI);
+ break;
+ case GET:
+ default:
+ request = new HttpGet(requestURI);
+ break;
+ }
+ return request;
+ }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/Alias.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/Alias.java
new file mode 100644
index 0000000..e8a9f1a
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/Alias.java
@@ -0,0 +1,53 @@
+/*
+ * 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.knox.gateway.shell.alias;
+
+import org.apache.knox.gateway.shell.KnoxSession;
+
+public class Alias {
+
+ public static AbstractAliasRequest list(final KnoxSession session) {
+ return new ListRequest(session);
+ }
+
+ public static AbstractAliasRequest list(final KnoxSession session,
+ final String clusterName) {
+ return new ListRequest(session, clusterName);
+ }
+
+ public static AbstractAliasRequest add(final KnoxSession session, final String alias, final String pwd) {
+ return new PostRequest(session, alias, pwd);
+ }
+
+ public static AbstractAliasRequest add(final KnoxSession session,
+ final String clusterName,
+ final String alias,
+ final String pwd) {
+ return new PostRequest(session, clusterName, alias, pwd);
+ }
+
+ public static AbstractAliasRequest remove(final KnoxSession session, final String alias) {
+ return new DeleteRequest(session, alias);
+ }
+
+ public static AbstractAliasRequest remove(final KnoxSession session,
+ final String clusterName,
+ final String alias) {
+ return new DeleteRequest(session, clusterName, alias);
+ }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/AliasResponse.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/AliasResponse.java
new file mode 100644
index 0000000..544764f
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/AliasResponse.java
@@ -0,0 +1,28 @@
+/*
+ * 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.knox.gateway.shell.alias;
+
+import org.apache.http.HttpResponse;
+import org.apache.knox.gateway.shell.BasicResponse;
+
+public class AliasResponse extends BasicResponse {
+
+ AliasResponse(HttpResponse response) {
+ super(response);
+ }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/DeleteRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/DeleteRequest.java
new file mode 100644
index 0000000..28101c0
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/DeleteRequest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.knox.gateway.shell.alias;
+
+import org.apache.knox.gateway.shell.KnoxSession;
+
+import java.util.List;
+
+public class DeleteRequest extends AbstractAliasRequest {
+
+ private String alias;
+
+ DeleteRequest(final KnoxSession session, final String alias) {
+ this(session, null, alias);
+ }
+
+ DeleteRequest(final KnoxSession session, final String clusterName, final String alias) {
+ this(session, clusterName, alias, null);
+ }
+
+ DeleteRequest(final KnoxSession session,
+ final String clusterName,
+ final String alias,
+ final String doAsUser) {
+ super(session, clusterName, doAsUser);
+ this.alias = alias;
+ requestURI = buildURI();
+ }
+
+ @Override
+ protected RequestType getRequestType() {
+ return RequestType.DELETE;
+ }
+
+ @Override
+ protected List<String> getPathElements() {
+ List<String> elements = super.getPathElements();
+ elements.add("/");
+ elements.add(alias);
+ return elements;
+ }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/ListRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/ListRequest.java
new file mode 100644
index 0000000..7b0f33d
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/ListRequest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.knox.gateway.shell.alias;
+
+import org.apache.knox.gateway.shell.KnoxSession;
+
+public class ListRequest extends AbstractAliasRequest {
+
+ ListRequest(KnoxSession session) {
+ this(session, null);
+ }
+
+ ListRequest(final KnoxSession session, final String clusterName) {
+ this(session, clusterName, null);
+ }
+
+ ListRequest(final KnoxSession session, final String clusterName, final String doAsUser) {
+ super(session, clusterName, doAsUser);
+ requestURI = buildURI();
+ }
+
+ @Override
+ protected RequestType getRequestType() {
+ return RequestType.GET;
+ }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/PostRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/PostRequest.java
new file mode 100644
index 0000000..11af9c1
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/alias/PostRequest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.knox.gateway.shell.alias;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.knox.gateway.shell.KnoxSession;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PostRequest extends AbstractAliasRequest {
+
+ static final String FORM_PARAM_VALUE = "value";
+
+ private String alias;
+ private String pwd;
+
+ PostRequest(final KnoxSession session, final String alias, final String pwd) {
+ this(session, null, alias, pwd);
+ }
+
+ PostRequest(final KnoxSession session, final String clusterName, final String alias, final String pwd) {
+ this(session, clusterName, alias, pwd, null);
+ }
+
+ PostRequest(final KnoxSession session,
+ final String clusterName,
+ final String alias,
+ final String pwd,
+ final String doAsUser) {
+ super(session, clusterName, doAsUser);
+ this.alias = alias;
+ this.pwd = pwd;
+ requestURI = buildURI();
+ }
+
+ @Override
+ protected RequestType getRequestType() {
+ return RequestType.POST;
+ }
+
+ @Override
+ protected List<String> getPathElements() {
+ List<String> elements = super.getPathElements();
+ elements.add("/");
+ elements.add(alias);
+ return elements;
+ }
+
+ @Override
+ protected HttpRequestBase createRequest() {
+ HttpRequestBase request = super.createRequest();
+ List<NameValuePair> formData = new ArrayList<>();
+ formData.add(new BasicNameValuePair(FORM_PARAM_VALUE, pwd));
+ ((HttpPost) request).setEntity(new UrlEncodedFormEntity(formData, StandardCharsets.UTF_8));
+ return request;
+ }
+
+}
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/alias/AliasTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/alias/AliasTest.java
new file mode 100644
index 0000000..0a9f900
--- /dev/null
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/alias/AliasTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.knox.gateway.shell.alias;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequest;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.util.EntityUtils;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class AliasTest {
+
+ @Test
+ public void testAddGatewayAlias() {
+ KnoxSession session = null;
+ try {
+ session = createMockKnoxSession();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+
+ final String alias = "aliasPut1";
+ final String pwd = "aliasPut1_pwd";
+
+ final String expectedEndpointPath =
+ session.base() + AbstractAliasRequest.SERVICE_PATH + "/" + AbstractAliasRequest.GATEWAY_CLUSTER_NAME + "/" + alias;
+
+ AbstractAliasRequest request = Alias.add(session, alias, pwd);
+ assertTrue(request instanceof PostRequest);
+ assertEquals("Endpoint mismatch", expectedEndpointPath, request.getRequestURI().toString());
+
+ Callable callable = request.callable();
+ try {
+ callable.call();
+ } catch (Exception e) {
+ // expected
+ }
+
+ assertEquals("Unexpected HTTP method.", HttpPost.METHOD_NAME, request.getRequest().getMethod());
+
+ HttpRequestBase httpRequest = request.getRequest();
+ assertTrue(httpRequest instanceof HttpPost);
+ HttpEntity entity = ((HttpPost) httpRequest).getEntity();
+ assertNotNull("Missing expected form data.", entity);
+ assertTrue(entity instanceof UrlEncodedFormEntity);
+ String formData = null;
+ try {
+ formData = EntityUtils.toString(entity);
+ } catch (IOException e) {
+ fail("Failed to consume request entity: " + e.getMessage());
+ }
+ assertNotNull(formData);
+ assertEquals("Form data mismatch",
+ PostRequest.FORM_PARAM_VALUE + "=" + pwd,
+ formData);
+ }
+
+ @Test
+ public void testAddClusterAlias() {
+ KnoxSession session = null;
+ try {
+ session = createMockKnoxSession();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+
+ final String clusterName = "myCluster";
+ final String alias = "aliasPut1";
+ final String pwd = "aliasPut1_pwd";
+
+ final String expectedEndpointPath = session.base() + AbstractAliasRequest.SERVICE_PATH + "/" + clusterName + "/" + alias;
+
+ AbstractAliasRequest request = Alias.add(session, clusterName, alias, pwd);
+ assertTrue(request instanceof PostRequest);
+ assertEquals("Endpoint mismatch", expectedEndpointPath, request.getRequestURI().toString());
+
+ Callable callable = request.callable();
+ try {
+ callable.call();
+ } catch (Exception e) {
+ // expected
+ }
+
+ assertEquals("Unexpected HTTP method.", HttpPost.METHOD_NAME, request.getRequest().getMethod());
+
+ HttpRequestBase httpRequest = request.getRequest();
+ assertTrue(httpRequest instanceof HttpPost);
+ HttpEntity entity = ((HttpPost) httpRequest).getEntity();
+ assertNotNull("Missing expected form data.", entity);
+ assertTrue(entity instanceof UrlEncodedFormEntity);
+ String formData = null;
+ try {
+ formData = EntityUtils.toString(entity);
+ } catch (IOException e) {
+ fail("Failed to consume request entity: " + e.getMessage());
+ }
+ assertNotNull(formData);
+ assertEquals("Form data mismatch",
+ PostRequest.FORM_PARAM_VALUE + "=" + pwd,
+ formData);
+ }
+
+ @Test
+ public void testRemoveGatewayAlias() {
+ KnoxSession session = null;
+ try {
+ session = createMockKnoxSession();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+
+ final String alias = "aliasPut1";
+
+ final String expectedEndpointPath =
+ session.base() + AbstractAliasRequest.SERVICE_PATH + "/" + AbstractAliasRequest.GATEWAY_CLUSTER_NAME + "/" + alias;
+
+ AbstractAliasRequest request = Alias.remove(session, alias);
+ assertTrue(request instanceof DeleteRequest);
+ assertEquals("Endpoint mismatch", expectedEndpointPath, request.getRequestURI().toString());
+
+ Callable callable = request.callable();
+ try {
+ callable.call();
+ } catch (Exception e) {
+ // expected
+ }
+
+ assertEquals("Unexpected HTTP method.", HttpDelete.METHOD_NAME, request.getRequest().getMethod());
+ }
+
+ @Test
+ public void testRemoveClusterAlias() {
+ KnoxSession session = null;
+ try {
+ session = createMockKnoxSession();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+
+ final String clusterName = "myCluster";
+ final String alias = "aliasPut1";
+
+ final String expectedEndpointPath = session.base() + AbstractAliasRequest.SERVICE_PATH + "/" + clusterName + "/" + alias;
+
+ AbstractAliasRequest request = Alias.remove(session, clusterName, alias);
+ assertTrue(request instanceof DeleteRequest);
+ assertEquals("Endpoint mismatch", expectedEndpointPath, request.getRequestURI().toString());
+
+ Callable callable = request.callable();
+ try {
+ callable.call();
+ } catch (Exception e) {
+ // expected
+ }
+
+ assertEquals("Unexpected HTTP method.", HttpDelete.METHOD_NAME, request.getRequest().getMethod());
+ }
+
+ @Test
+ public void testListGatewayAliases() {
+ KnoxSession session = null;
+ try {
+ session = createMockKnoxSession();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+
+ final String expectedEndpointPath =
+ session.base() + AbstractAliasRequest.SERVICE_PATH + "/" + AbstractAliasRequest.GATEWAY_CLUSTER_NAME;
+
+ AbstractAliasRequest request = Alias.list(session);
+ assertTrue(request instanceof ListRequest);
+ assertEquals("Endpoint mismatch", expectedEndpointPath, request.getRequestURI().toString());
+
+ Callable callable = request.callable();
+ try {
+ callable.call();
+ } catch (Exception e) {
+ // expected
+ }
+
+ assertEquals("Unexpected HTTP method.", HttpGet.METHOD_NAME, request.getRequest().getMethod());
+ }
+
+
+ @Test
+ public void testListClusterAliases() {
+ KnoxSession session = null;
+ try {
+ session = createMockKnoxSession();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+
+ final String clusterName = "myCluster";
+ final String expectedEndpointPath = session.base() + AbstractAliasRequest.SERVICE_PATH + "/" + clusterName;
+
+ AbstractAliasRequest request = Alias.list(session, clusterName);
+ assertTrue(request instanceof ListRequest);
+ assertEquals("Endpoint mismatch", expectedEndpointPath, request.getRequestURI().toString());
+
+ Callable callable = request.callable();
+ try {
+ callable.call();
+ } catch (Exception e) {
+ // expected
+ }
+
+ assertEquals("Unexpected HTTP method.", HttpGet.METHOD_NAME, request.getRequest().getMethod());
+ }
+
+
+ private KnoxSession createMockKnoxSession() throws Exception {
+ KnoxSession knoxSession = createMock(KnoxSession.class);
+ expect(knoxSession.base()).andReturn("http://localhost/base").atLeastOnce();
+ expect(knoxSession.getHeaders()).andReturn(Collections.emptyMap()).atLeastOnce();
+ expect(knoxSession.executeNow(isA(HttpRequest.class))).andReturn(null).atLeastOnce();
+ replay(knoxSession);
+ return knoxSession;
+ }
+
+
+}