Skip to content
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

feat: new and initialize commands for migration tool #6988

Merged
merged 10 commits into from
Feb 17, 2021

Conversation

jzaralim
Copy link
Contributor

Description

new: creates a new directory and empty properties file
initialize: creates migration stream and table

Reviewer checklist

  • Ensure docs are updated if necessary. (eg. if a user visible feature is being added or changed).
  • Ensure relevant issues are linked (description should include text like "Fixes #")

@jzaralim jzaralim requested a review from a team as a code owner February 10, 2021 17:03
Copy link
Contributor

@vcrfxia vcrfxia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jzaralim -- awesome stuff!! Comments inline ; nothing major.

System.out.println(eventStreamCommand);
System.out.println(versionTableCommand);
} else {
final KsqlRestClient client = KsqlRestClient.create(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we can't use the actual Java client here? Unless we can think of downsides, it'd be great to dogfood. Plus we'd like to eventually migrate the CLI to use the Java client (and away from KsqlRestClient) as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, but for now KsqlRestClient is used in MigrationConfig to query the /info endpoint. This is temporary until the Java client supports this functionality.

Files.createDirectories(Paths.get(projectPath));
new File(projectPath + "/ksql-migrations.properties").createNewFile();
} catch (IOException e) {
e.printStackTrace();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't have to be in this PR, but we should make sure we sanity-check the error messages users get if the tool doesn't have permissions to create the files/directories, or if file/directories already exist. We should also sanity check the tool's behavior (do we overwrite existing files, etc).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 not only that, we should return correct error codes (using System.exit) if necessary so that it can be properly piped into scripts. I think getting error messages correct is a huge part of making this tool successful.

cc @colinhicks @spena for their opinion on how this should work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also recommend avoiding error stacks in the output that users see (logs can have the error messages). This turns people off from these tools and makes them look very rough around the edges.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, very important to return the correct error codes from the process. This is relevant for CI, too, where a non-zero code is expected to signal job step failure. We want to avoid forcing users to otherwise detect failure by parsing the output.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. I also usually avoid putting everything into one try/catch when I want to know what failed. This way I can display something like:

$ ksql-migrations new /unknown-dir/migrations-project
Creating directory: <project-dir>
Failed creating directory: "Directory /unknown-dir does not exist"

Which then you can return know what error code to return, like System.exit(2). This number will let user know what's wrong when writing scripts.

When working with user tools, it is always recommended to display a good error message and an error status. They will thank you a lot if they write their on bash scripts to automate this tool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. For now, they all have the same error code, but after more parts of the tool are complete we can go through the errors and categorize them.

System.out.println("Successfully created migration stream");
} else {
System.out.println("Failed to create migration stream: "
+ streamResponse.getErrorMessage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should sanity-check the error message the user gets if they try calling initialize twice (i.e., the stream already exists). It may make sense to check for the existence of the stream/table ahead of issuing the create calls and print a more targeted error message in that case. The targeted error message could mention the clean command which can be used if the user wants to start over with migrations. (This command could also help get them out of situations where for whatever reason the stream was created but the table wasn't.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

System.out.println(eventStreamCommand);
System.out.println(versionTableCommand);
} else {
final KsqlRestClient client = KsqlRestClient.create(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

System.out.println("Successfully created migration stream");
} else {
System.out.println("Failed to create migration stream: "
+ streamResponse.getErrorMessage());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

throw new UnsupportedOperationException();
try {
Files.createDirectories(Paths.get(projectPath));
new File(projectPath + "/ksql-migrations.properties").createNewFile();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new config should have some default properties, and perhaps commented properties names in case the user wants to customize it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 but I was going to suggest this as a follow-up. I think we should update the new command to take in the ksqlDB server URL, which we can then pre-populate into the config file. This is the only required config in the config file, so we definitely want it.

I don't feel strongly about whether we populate some of the other configs with defaults or not. Could be nice for some of the ones we expect users are more likely to want to change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but I'll leave this as a follow-up

Files.createDirectories(Paths.get(projectPath));
new File(projectPath + "/ksql-migrations.properties").createNewFile();
} catch (IOException e) {
e.printStackTrace();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. I also usually avoid putting everything into one try/catch when I want to know what failed. This way I can display something like:

$ ksql-migrations new /unknown-dir/migrations-project
Creating directory: <project-dir>
Failed creating directory: "Directory /unknown-dir does not exist"

Which then you can return know what error code to return, like System.exit(2). This number will let user know what's wrong when writing scripts.

When working with user tools, it is always recommended to display a good error message and an error status. They will thank you a lot if they write their on bash scripts to automate this tool.

Copy link
Contributor

@vcrfxia vcrfxia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jzaralim -- looking great! A few suggestions and minor fixes inline. Feel free to address as follow-ups. LGTM assuming everything gets addressed.

client.close();

if (!response.isSuccessful()) {
LOGGER.error("Failed to query " + MigrationConfig.KSQL_SERVER_URL + "/info");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry about this for now since this code is going to be replaced with Java client code anyway, but after the replacement we should verify that this error message is actionable. For example, the current error message doesn't give any information regarding why the failure occurred (auth issue, transient network issue, invalid address, etc).


@Override
public void run() {
final long startTime = System.currentTimeMillis();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should refactor this time-tracking into BaseCommand since it's common to all commands. Feel free to postpone to a follow-up if you prefer.

.setHost(url.getHost())
.setPort(url.getPort());

final Client ksqlClient = Client.create(options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating the Java client from the MigrationsConfig should be factored out into a util file, since many more commands will need this. Feel free to defer to a follow-up PR.

private boolean tryCreatePropertiesFile(final String path) {
final File configFile = new File(path);
if (configFile.exists()) {
LOGGER.info(path + " already exists. Skipping file creation.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log message should at least be a warning. It might make sense to even fail the command if the config file already exists, since it doesn't make sense for the user to run migrations new in this case, especially when we introduce a new param to specify the server URL as part of the command. (cc @colinhicks for a minor product question, in case he has input)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't all these messages go to the command output? If the command fails, it just exits with System.exit(1), and nothing shows the user what was wrong unless they go to the log.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to warn for now, might rethink this after adding the new param

public static final String KSQL_MIGRATIONS_TOPIC_REPLICAS = "ksql.migrations.topic.replicas";
public static final int KSQL_MIGRATIONS_TOPIC_REPLICAS_DEFAULT = 1;

private static final Logger LOGGER = LoggerFactory.getLogger(MigrationConfig.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What log4j config is being used with these loggers by default? Do we need to provide one with the tool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a log4j config file that defaults to stdout

Comment on lines 59 to 76
if (directory.exists() && directory.isDirectory()) {
LOGGER.info(path + " already exists. Skipping directory creation.");
return true;
} else if (directory.exists() && !directory.isDirectory()) {
LOGGER.error(path + " already exists as a file. Cannot create directory.");
return false;
}

try {
LOGGER.info("Creating directory: " + path);
Files.createDirectories(Paths.get(path));
} catch(FileSystemException e) {
LOGGER.error("Permission denied: create directory " + path);
return false;
} catch (IOException e) {
LOGGER.error(String.format("Failed to create directory %s: %s", path, e.getMessage()));
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't all these messages go to the command output? If an error occurs, the command will not show anything unless the user goes to the log.

tryCreateDirectory(projectPath + "/migrations") &&
tryCreatePropertiesFile(projectPath + "/ksql-migrations.properties")) {
final long endTime = System.currentTimeMillis();
LOGGER.info("Migrations project directory created successfully (execution time " +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused. Why is the message sent to the logger and not the command output?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, the log4j config that we provide defaults to stdout. We might revisit this later.

@jzaralim jzaralim merged commit 8bdb09a into confluentinc:master Feb 17, 2021
@jzaralim jzaralim deleted the initialize branch February 17, 2021 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants