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

Setting Overhaul #105

Closed
wants to merge 3 commits into from
Closed

Setting Overhaul #105

wants to merge 3 commits into from

Conversation

da2ce7
Copy link
Contributor

@da2ce7 da2ce7 commented Oct 24, 2022

  • Move internal vocabulary from 'Configuration' with 'Settings'.
  • Make use of a 'config' folder. Will generate new config files when needed.
  • New Config is stored in json. And parsed without the need of the config crate.
  • Automatically finds and moves old configuration to new format, (keeping a backup).

@da2ce7 da2ce7 force-pushed the setting-overhaul branch 3 times, most recently from 7dc2402 to 698f181 Compare October 24, 2022 20:15
src/logging.rs Outdated

if level == log::LevelFilter::Off {
return;
}

INIT.call_once(|| {
stdout_config(level);
stdout_settings(level);
Copy link
Member

Choose a reason for hiding this comment

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

hi @da2ce7, in this case, "config" is a verb. The function stdout_config sets up the logging. I was tempted to rename it to redirect_logging_to_stdout or something like that.

@josecelano
Copy link
Member

hi @da2ce7, it looks good to me. In the future, something that could be useful is to allow overwriting default settings from env vars instead of from a config/local.toml file.

I've seen that the crate we are using allows it.

I think the local.toml file is useful for development env and installations that are done manually. If you are using docker, it could be nice to overwrite the settings using env vars. Anyway, you can also mount the configuration as I did here:

https://github.com/josecelano/torrust-tracker/blob/issue-11-docker-support/bin/docker/run.sh#L6

And with K8S, you also have ConfigMaps. These are the ways that the container can get the configuration:

  • Inside a container command and args.
  • Environment variables for a container.
  • Add a file in read-only volume, for the application to read.
  • Write code to run inside the Pod that uses the Kubernetes API to read a ConfigMap.

There is also another thing to consider. Some settings are secrets. From the application point of view, I suppose it does not matter. If we are using docker, we can decide that the whole configuration is passed as a mounted read-only file or as env vars. I do not know if one could be considered safer than the other.

@josecelano
Copy link
Member

ACK 698f181

@da2ce7 da2ce7 marked this pull request as draft October 29, 2022 12:25
@da2ce7
Copy link
Contributor Author

da2ce7 commented Nov 6, 2022

Needs Rebase.

@da2ce7 da2ce7 force-pushed the setting-overhaul branch 2 times, most recently from 0fd33f2 to 3ab4828 Compare November 7, 2022 07:30
@da2ce7 da2ce7 marked this pull request as ready for review November 7, 2022 10:50
.vscode/settings.json Outdated Show resolved Hide resolved
Comment on lines 45 to 36
impl SqliteDatabase {
pub fn new(db_path: &str) -> Result<SqliteDatabase, r2d2::Error> {
let cm = SqliteConnectionManager::file(db_path);
pub fn new(settings: &Sqlite3DatabaseSettings) -> Result<SqliteDatabase, r2d2::Error> {
let cm = SqliteConnectionManager::file(settings.database_file_path.as_path());
let pool = Pool::new(cm).expect("Failed to create r2d2 SQLite connection pool.");
Ok(SqliteDatabase { pool })
}
Copy link
Member

Choose a reason for hiding this comment

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

hi @da2ce7, I always have doubts about this kind of change. I have a "rule" which is: "do not pass to a function more than what it needs". In this case, the constructor only needs the path for the DB file. In the first version, SqliteDatabase did not know anything about the Settings, and now they are coupled. If you change the settings you also have to change this function.

Normally you would do that because you think in the future, the SqliteDatabase might need other values.

Anyway, I think it's a nice generalization since we have more than one database driver. I would expect all drivers to have a similar constructor regardless of their specific parameters.

On the other hand, would it make sense to add a wrapper for the DB path like this:

pub struct Sqlite3DatabaseSettings {
    pub database_file_path: Sqlite3DatabaseFilePath,
}

pub struct Sqlite3DatabaseFilePath {
    pub file_path: PathBuf,
}

In general, I have another rule: "do not add a wrapper to a primitive type (or other types) if you do not have any rule (invariant) to enforce".

In this case, we do not have it unless we want to check that the file exists and contains SQLite data, which is, I think, a bad idea to do in the constructor. But maybe we can check that the path is for a file and not a directory.

But sometimes, I think these custom types are a good way to easily find where you are using them.

Anyway, I would probably do this:

pub fn new(db_file_path: &PathBuf) -> Result<SqliteDatabase, r2d2::Error> {
    let cm = SqliteConnectionManager::file(db_file_path.as_path());
    let pool = Pool::new(cm).expect("Failed to create r2d2 SQLite connection pool.");
        Ok(SqliteDatabase { pool })
    }

By the way, maybe we should use Path instead of PathBuf here. But I do not know very weel the difference. I think here we could use a Path because we do not need to change it.

}

#[derive(Error, Clone, Debug, Eq, Hash, PartialEq)]
pub enum CommonSettingsError {
Copy link
Member

Choose a reason for hiding this comment

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

hi @da2ce7, what about GenericSettingsErrors?


pub fn start_job(config: &Configuration, tracker: Arc<TorrentTracker>) -> JoinHandle<()> {
pub fn start_job(interval: u64, tracker: Arc<TorrentTracker>) -> JoinHandle<()> {
Copy link
Member

Choose a reason for hiding this comment

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

hi @da2ce7, this is an example where you decided to pass only what the function needs. I would like to know why you did it here.

.parse::<std::net::SocketAddr>()
.expect("Tracker API bind_address invalid.");
info!("Starting Torrust API server on: {}", bind_addr);
pub fn start_job(settings: &ApiServiceSettings, tracker: Arc<TorrentTracker>) -> JoinHandle<()> {
Copy link
Member

Choose a reason for hiding this comment

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

hi @da2ce7, Another example where you reduce the data you pass to the function.

}

#[test]
fn it_should_write_and_read_and_write_default_config() {
Copy link
Member

Choose a reason for hiding this comment

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

@da2ce7 Do you mean "it should migrate config file from toml to json"?

And there are no assertions.

}

#[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Hash)]
pub struct CommonSettings {
Copy link
Member

Choose a reason for hiding this comment

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

@da2ce7 what's the difference between "global" and "common"?


#[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Copy, Clone, Hash, Display)]
#[serde(rename_all = "snake_case")]
pub enum LogFilterLevel {
Copy link
Member

Choose a reason for hiding this comment

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

@da2ce7 move to logging?

CommonSettings, CommonSettingsBuilder, DatabaseSettingsBuilder, GlobalSettings, GlobalSettingsBuilder, LogFilterLevel,
};

pub struct TrackerArgs {
Copy link
Member

Choose a reason for hiding this comment

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

@da2ce7 I think this struct is useful now, but in the long term, I think we should extract some responsibilities from the Tracker, for example persistence. I think we could have:

  • One handler or controller fer each request type (announce, scrape, ...)
  • An app service (announce is the same from HTTP and udp). It could be also a command or query with its handler. For example: AnnounceCommand with AnnounceCommandHandler.
  • A TorrentService to add/update/remove torrents. We can trigger events at this level.
  • The TorrentService can use a TorrentRepository (DB: SQLite or MySQL) and a TorrentCache (the current in-memory structure).
    But it's just a vague idea I have. I wanted to draft something, but I have not had time. And I also wanted to get to know the repo better before spending time proposing this kind of change.

tests/udp.rs Outdated
@@ -149,8 +171,8 @@ mod udp_tracker_server {
}

/// Creates a new UdpTrackerClient connected to a Udp Tracker server
async fn new_connected_udp_tracker_client(remote_address: &str) -> UdpTrackerClient {
let udp_client = new_connected_udp_client(remote_address).await;
async fn new_connected_udp_tracker_client(remote_socket: &SocketAddr) -> UdpTrackerClient {
Copy link
Member

Choose a reason for hiding this comment

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

@da2ce7 I like this change. We should try to normalize this variable name. We are using:

  • remote_address
  • remote_socket
  • remote_ip
  • remote_client
  • client?

I think client_socket is better because it's more specific. But I have not changed to avoid introducing more versions.

And I've just realized in this case, it's the server_socket which makes my assumptions more valid that we should be more specific.

@josecelano
Copy link
Member

hi @da2ce7, it looks good to me! I've just added some minor comments. I agree on all refactors, but I'm not sure why you want to change the config file from toml to json. I do not see any reason to do it but do not see any reason not to do it, either.

Copy link
Member

@josecelano josecelano left a comment

Choose a reason for hiding this comment

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

It looks to me, although I still do not know the reason for using JSON instead of TOML. is it only because you want to remove the dependency?

@da2ce7 da2ce7 marked this pull request as draft November 30, 2022 17:17
@da2ce7
Copy link
Contributor Author

da2ce7 commented Nov 30, 2022

Considering to Rewrite this entire thing all-again. This time making full use of https://github.com/SergioBenitez/Figment

@josecelano josecelano mentioned this pull request Dec 15, 2022
17 tasks
@da2ce7 da2ce7 added the Needs Rebase Base Branch has Incompatibilities label Dec 22, 2022
@da2ce7 da2ce7 removed the Needs Rebase Base Branch has Incompatibilities label Feb 10, 2023
@da2ce7
Copy link
Contributor Author

da2ce7 commented Feb 10, 2023

Partial Rebase. Completed Database Settings.

@da2ce7
Copy link
Contributor Author

da2ce7 commented Sep 6, 2023

Closing, needs to be re-imagined, suggest using https://github.com/SergioBenitez/Figment as config framework library.

@da2ce7 da2ce7 closed this Sep 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

2 participants