-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Conversation
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.
Thanks @jzaralim -- awesome stuff!! Comments inline ; nothing major.
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/MigrationConfig.java
Outdated
Show resolved
Hide resolved
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/MigrationConfig.java
Outdated
Show resolved
Hide resolved
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/MigrationConfig.java
Outdated
Show resolved
Hide resolved
...ls/src/main/java/io/confluent/ksql/tools/migrations/commands/InitializeMigrationCommand.java
Show resolved
Hide resolved
System.out.println(eventStreamCommand); | ||
System.out.println(versionTableCommand); | ||
} else { | ||
final KsqlRestClient client = KsqlRestClient.create( |
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.
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.
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.
+1
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.
+1
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.
Done, but for now KsqlRestClient
is used in MigrationConfig
to query the /info
endpoint. This is temporary until the Java client supports this functionality.
...ls/src/main/java/io/confluent/ksql/tools/migrations/commands/InitializeMigrationCommand.java
Outdated
Show resolved
Hide resolved
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/commands/NewMigrationCommand.java
Outdated
Show resolved
Hide resolved
Files.createDirectories(Paths.get(projectPath)); | ||
new File(projectPath + "/ksql-migrations.properties").createNewFile(); | ||
} catch (IOException e) { | ||
e.printStackTrace(); |
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.
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).
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.
+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.
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'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.
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.
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.
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.
+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.
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.
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()); |
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.
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.)
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.
+1
...ls/src/main/java/io/confluent/ksql/tools/migrations/commands/InitializeMigrationCommand.java
Outdated
Show resolved
Hide resolved
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/MigrationConfig.java
Outdated
Show resolved
Hide resolved
System.out.println(eventStreamCommand); | ||
System.out.println(versionTableCommand); | ||
} else { | ||
final KsqlRestClient client = KsqlRestClient.create( |
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.
+1
...ls/src/main/java/io/confluent/ksql/tools/migrations/commands/InitializeMigrationCommand.java
Outdated
Show resolved
Hide resolved
...ls/src/main/java/io/confluent/ksql/tools/migrations/commands/InitializeMigrationCommand.java
Outdated
Show resolved
Hide resolved
System.out.println("Successfully created migration stream"); | ||
} else { | ||
System.out.println("Failed to create migration stream: " | ||
+ streamResponse.getErrorMessage()); |
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.
+1
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/commands/NewMigrationCommand.java
Outdated
Show resolved
Hide resolved
throw new UnsupportedOperationException(); | ||
try { | ||
Files.createDirectories(Paths.get(projectPath)); | ||
new File(projectPath + "/ksql-migrations.properties").createNewFile(); |
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.
The new config should have some default properties, and perhaps commented properties names in case the user wants to customize it.
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.
+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.
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.
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(); |
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.
+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.
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/commands/NewMigrationCommand.java
Outdated
Show resolved
Hide resolved
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.
Thanks @jzaralim -- looking great! A few suggestions and minor fixes inline. Feel free to address as follow-ups. LGTM assuming everything gets addressed.
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/MigrationConfig.java
Outdated
Show resolved
Hide resolved
client.close(); | ||
|
||
if (!response.isSuccessful()) { | ||
LOGGER.error("Failed to query " + MigrationConfig.KSQL_SERVER_URL + "/info"); |
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.
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(); |
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.
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); |
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.
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."); |
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.
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)
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.
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.
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.
changed to warn for now, might rethink this after adding the new param
ksqldb-tools/src/main/java/io/confluent/ksql/tools/migrations/commands/NewMigrationCommand.java
Show resolved
Hide resolved
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); |
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.
What log4j config is being used with these loggers by default? Do we need to provide one with the tool?
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.
Added a log4j config file that defaults to stdout
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; | ||
} |
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.
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 " + |
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 am confused. Why is the message sent to the logger and not the command output?
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.
For now, the log4j config that we provide defaults to stdout. We might revisit this later.
Description
new: creates a new directory and empty properties file
initialize: creates migration stream and table
Reviewer checklist