Skip to content

Commit

Permalink
KNOX-3002 - KnoxCLI command for generating descriptor for a role type…
Browse files Browse the repository at this point in the history
… from a list of hosts (#835)
  • Loading branch information
zeroflag authored Feb 26, 2024
1 parent d60c67f commit bb5d265
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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.util;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FilenameUtils;
import org.apache.knox.gateway.model.DescriptorConfiguration;
import org.apache.knox.gateway.model.Topology;

public class DescriptorGenerator {
private static final ObjectMapper mapper = new ObjectMapper();
private final String descriptorName;
private final String providerName;
private final String serviceName;
private final ServiceUrls serviceUrls;
private final Map<String, String> params;

static {
/* skip printing out null fields */
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}

public DescriptorGenerator(String descriptorName, String providerName, String serviceName, ServiceUrls serviceUrls, Map<String, String> params) {
this.descriptorName = descriptorName;
this.providerName = providerName;
this.serviceName = serviceName.toUpperCase(Locale.ROOT);
this.serviceUrls = serviceUrls;
this.params = params;
}

public void saveDescriptor(File outputDir, boolean forceOverwrite) {
File outputFile = new File(outputDir, descriptorName);
if (outputFile.exists() && !forceOverwrite) {
throw new IllegalArgumentException(outputFile + " already exists");
}
DescriptorConfiguration descriptor = new DescriptorConfiguration();
descriptor.setName(FilenameUtils.removeExtension(descriptorName));
descriptor.setProviderConfig(FilenameUtils.removeExtension(providerName));
Topology.Service service = new Topology.Service();
service.setRole(serviceName);
service.setUrls(serviceUrls.toList());
setParams(service, params);
descriptor.setServices(Arrays.asList(service));
try {
mapper.writerWithDefaultPrettyPrinter()
.writeValue(outputFile, descriptor);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private void setParams(Topology.Service service, Map<String, String> params) {
List<Topology.Param> paramList = new ArrayList<>();
for (Map.Entry<String, String> each : params.entrySet()) {
paramList.add(new Topology.Param(each.getKey(), each.getValue()));
}
service.setParams(paramList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -134,6 +135,7 @@ public class KnoxCLI extends Configured implements Tool {
" [" + RemoteRegistryGetACLCommand.USAGE + "]\n" +
" [" + TopologyConverter.USAGE + "]\n" +
" [" + JWKGenerator.USAGE + "]\n" +
" [" + GenerateDescriptorCommand.USAGE + "]\n" +
" [" + TokenMigration.USAGE + "]\n";

/** allows stdout to be captured if necessary */
Expand Down Expand Up @@ -173,6 +175,9 @@ public class KnoxCLI extends Configured implements Tool {
private String discoveryUser;
private String discoveryPasswordAlias;
private String discoveryType;
private String serviceName;
private String urlsFilePath;
private final Map<String, String> params = new TreeMap<>();

// For testing only
private String master;
Expand Down Expand Up @@ -388,11 +393,23 @@ private int init(String[] args) throws IOException {
}
this.topologyName = args[++i];
} else if (args[i].equals("--descriptor-name")) {
if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
printKnoxShellUsage();
return -1;
}
this.descriptorName = args[++i];
} else if (args[i].equals("--service-name")) {
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
printKnoxShellUsage();
return -1;
}
this.serviceName = args[++i];
} else if (args[i].equals("--service-urls-file")) {
if (i + 1 >= args.length || args[i + 1].startsWith("-")) {
printKnoxShellUsage();
return -1;
}
this.urlsFilePath = args[++i];
} else if (args[i].equals("--output-dir")) {
if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
printKnoxShellUsage();
Expand Down Expand Up @@ -519,8 +536,21 @@ private int init(String[] args) throws IOException {
} else if (args[i].equalsIgnoreCase("convert-topology")) {
if (args.length >= 5) {
command = new TopologyConverter();
} else {
printKnoxShellUsage();
return -1;
}
else {
} else if (args[i].equalsIgnoreCase("generate-descriptor")) {
if (args.length >= 7) {
command = new GenerateDescriptorCommand();
} else {
printKnoxShellUsage();
return -1;
}
} else if (args[i].equalsIgnoreCase("--param")) {
if (i + 2 < args.length) {
params.put(args[++i], args[++i]);
} else {
printKnoxShellUsage();
return -1;
}
Expand Down Expand Up @@ -2315,7 +2345,7 @@ public class TopologyConverter extends Command {

public static final String USAGE =
"convert-topology --path \"path/to/topology.xml\" --provider-name my-provider.json [--descriptor-name my-descriptor.json] "
+ "[--topology-name topologyName] [--output-path \"path/to/configs/\"] [--force] [--cluster clusterName] [--discovery-url url] "
+ "[--topology-name topologyName] [--output-dir \"path/to/configs/\"] [--force] [--cluster clusterName] [--discovery-url url] "
+ "[--discovery-user discoveryUser] [--discovery-pwd-alias discoveryPasswordAlias] [--discovery-type discoveryType]";
public static final String DESC =
"Convert Knox topology file to provider and descriptor config files \n"
Expand All @@ -2325,7 +2355,7 @@ public class TopologyConverter extends Command {
+ "--descriptor-name (optional) name of descriptor json config file (including .json extension) \n"
+ "--topology-name (optional) topology-name can be use instead of --path option, if used, KnoxCLI will attempt to find topology from deployed topologies.\n"
+ "\t if not provided topology name will be used as descriptor name \n"
+ "--output-dir (optional) output directory to save provider and descriptor config files \n"
+ "--output-dir (optional) output directory to save provider and descriptor config files. Default is the current working directory. \n"
+ "\t if not provided config files will be saved in appropriate Knox config directory \n"
+ "--force (optional) force rewriting of existing files, if not used, command will fail when the configs files with same name already exist. \n"
+ "--cluster (optional) cluster name, required for service discovery \n"
Expand Down Expand Up @@ -2409,6 +2439,59 @@ public String getUsage() {

}

public class GenerateDescriptorCommand extends Command {

public static final String USAGE =
"generate-descriptor --service-urls-file \"path/to/urls.txt\" --service-name SERVICE_NAME \n" +
"--provider-name my-provider.json --descriptor-name my-descriptor.json \n" +
"[--output-dir /path/to/output_dir] \n" +
"[--param key1 value1] \n" +
"[--param key2 value2] \n" +
"[--force] \n";
public static final String DESC =
"Create Knox topology descriptor file for one service\n"
+ "Options are as follows: \n"
+ "--service-urls-file (required) path to a text file containing service urls \n"
+ "--service-name (required) the name of the service, such as WEBHDFS, IMPALAUI or HIVE \n"
+ "--descriptor-name (required) name of descriptor to be created \n"
+ "--provider-name (required) name of the referenced shared provider \n"
+ "--output-dir (optional) output directory to save the descriptor file \n"
+ "--param (optional) service param name and value \n"
+ "--force (optional) force rewriting of existing files, if not used, command will fail when the configs files with same name already exist. \n";

@Override
public void execute() throws Exception {
validateParams();
File output = StringUtils.isBlank(outputDir) ? new File(".") : new File(outputDir);
DescriptorGenerator generator =
new DescriptorGenerator(descriptorName, providerName, serviceName, ServiceUrls.fromFile(urlsFilePath), params);
generator.saveDescriptor(output, force);
out.println("Descriptor " + descriptorName + " was successfully saved to " + output.getAbsolutePath() + "\n");
}

private void validateParams() {
if (StringUtils.isBlank(FilenameUtils.getExtension(providerName))
|| StringUtils.isBlank(FilenameUtils.getExtension(descriptorName))) {
throw new IllegalArgumentException("JSON extension is required for provider and descriptor file names");
}
if (StringUtils.isBlank(urlsFilePath) ) {
throw new IllegalArgumentException("Missing --service-urls-file");
}
if (!new File(urlsFilePath).isFile()) {
throw new IllegalArgumentException(urlsFilePath + " does not exist");
}
if (StringUtils.isBlank(serviceName)) {
throw new IllegalArgumentException("Missing --service-name");
}
}

@Override
public String getUsage() {
return USAGE + ":\n\n" + DESC;
}

}

public class JWKGenerator extends Command {

public static final String USAGE = "generate-jwk [--jwkAlg HS256|HS384|HS512] [--saveAlias alias] [--topology topology]";
Expand Down
Original file line number Diff line number Diff line change
@@ -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.util;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

public class ServiceUrls {
private final List<String> urls;

public static ServiceUrls fromFile(String urlsFilePath) {
return fromFile(new File(urlsFilePath));
}

public static ServiceUrls fromFile(File urlsFilePath) {
try {
List<String> lines = FileUtils.readLines(urlsFilePath, Charset.defaultCharset()).stream()
.map(String::trim)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
return new ServiceUrls(lines);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public ServiceUrls(List<String> urls) {
this.urls = urls;
}

public List<String> toList() {
return Collections.unmodifiableList(urls);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.util;

import static org.junit.Assert.assertEquals;

import java.io.File;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.knox.gateway.model.DescriptorConfiguration;
import org.apache.knox.gateway.model.Topology;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class DescriptorGeneratorTest {
private static final ObjectMapper mapper = new ObjectMapper();
private static final String TEST_DESC_1 = "test_desc1.json";
private static final String TEST_PROV_1 = "test_prov1.json";
private static final String IMPALA_UI = "IMPALAUI";
private static final List<String> URLS =
Arrays.asList("http://amagyar-1.test.site:25000/", "http://amagyar-2.test.site:25000");

private static final Map<String,String> PARAMS = new HashMap<>();
static { PARAMS.put("KEY_1", "VAL_1"); }

@Rule
public TemporaryFolder folder= new TemporaryFolder();

@Test
public void testCreateDescriptor() throws Exception {
DescriptorGenerator generator = new DescriptorGenerator(TEST_DESC_1, TEST_PROV_1, IMPALA_UI, new ServiceUrls(URLS), PARAMS);
File outputDir = folder.newFolder().getAbsoluteFile();
File outputFile = new File(outputDir, TEST_DESC_1);
generator.saveDescriptor(outputDir, false);
System.out.println(FileUtils.readFileToString(outputFile, Charset.defaultCharset()));
DescriptorConfiguration result = mapper
.readerFor(DescriptorConfiguration.class)
.readValue(outputFile);
assertEquals(FilenameUtils.removeExtension(TEST_PROV_1), result.getProviderConfig());
assertEquals(FilenameUtils.removeExtension(TEST_DESC_1), result.getName());
assertEquals(1, result.getServices().size());
Topology.Service service = result.getServices().get(0);
assertEquals(IMPALA_UI, service.getRole());
assertEquals(URLS, service.getUrls());
assertEquals(1, service.getParams().size());
assertEquals("KEY_1", service.getParams().get(0).getName());
assertEquals("VAL_1", service.getParams().get(0).getValue());
}

@Test(expected = IllegalArgumentException.class)
public void testOutputAlreadyExists() throws Exception {
DescriptorGenerator generator = new DescriptorGenerator(TEST_DESC_1, TEST_PROV_1, IMPALA_UI, new ServiceUrls(URLS), PARAMS);
File outputDir = folder.newFolder().getAbsoluteFile();
File outputFile = new File(outputDir, TEST_DESC_1);
outputFile.createNewFile();
generator.saveDescriptor(outputDir, false);
}
}

0 comments on commit bb5d265

Please sign in to comment.