-
Notifications
You must be signed in to change notification settings - Fork 897
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
DB Backups to AWS S3 #17689
DB Backups to AWS S3 #17689
Conversation
@miq-bot add_label WIP |
lib/evm_database_ops.rb
Outdated
end | ||
|
||
block_result = yield db_opts if block_given? | ||
uri || block_result | ||
ensure | ||
session.disconnect if session | ||
session&.add(db_opts[:local_file], connect_opts[:uri], db_opts[:object_file]) if action == :backup && session.class.name == "MiqS3Session" |
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 don't think this belongs in an ensure
block as if an error occurred there's no guarantee that a backup was produced.
lib/evm_database_ops.rb
Outdated
@@ -115,14 +116,18 @@ def self.restore(db_opts, connect_opts = {}) | |||
uri = File.join(connect_opts[:uri], backup_folder, connect_opts[:remote_file_name]) | |||
end | |||
|
|||
connect_opts[:username] = db_opts[:username] if connect_opts[:username].nil? | |||
connect_opts[:password] = db_opts[:password] if connect_opts[:password].nil? |
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.
Why would we want to assume the connection options to the s3 store would ever be the same as the database authentication values?
lib/evm_database_ops.rb
Outdated
BACKUP_TMP_FILE = "/tmp/miq_backup".freeze | ||
DUMP_TMP_FILE = "/tmp/miq_pg_dump".freeze | ||
BACKUP_TMP_FILE = "/tmp/miq_backup".freeze | ||
DUMP_TMP_FILE = "/tmp/miq_pg_dump".freeze |
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.
Why was this changed?
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 had another line in there that required aligning these lines. When I removed it I failed to undo the indentation. I'll fix.
app/models/file_depot_s3.rb
Outdated
private | ||
|
||
def create_bucket(directory_path) | ||
if (ssa_bucket = s3.bucket(directory_path)).exists? |
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 does ssa_bucket
mean here? I don't think this has anything to do with Smart State Analysis right?
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.
Absolutely correct.
app/models/file_depot_s3.rb
Outdated
base_path.join(file.destination_directory) | ||
end | ||
|
||
def base_path |
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 don't think base path is a good name for this. In fact I would assume the "base" of a URL would mean the directory or URL host, not the file name.
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.
See file_depot_ftp.rb. Same code.
app/models/file_depot_s3.rb
Outdated
@@ -0,0 +1,54 @@ | |||
class FileDepotS3 < FileDepot | |||
include AmazonAuthenticationMixin |
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 is this and why doesn't the regular AuthenticaionMixin
do what you need?
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.
Because it doesn't understand Amazon connectivity. This is pulled in from the Amazon Provider mixin.
lib/evm_database_ops.rb
Outdated
db_opts[:local_file] = session.uri_to_local_path(uri) | ||
db_opts[:local_file] = session.uri_to_local_path(uri) | ||
db_opts[:object_file] = session.uri_to_object_path(uri) if connect_opts[:object_store] | ||
db_opts[:session] = session if db_opts[:session].class.name == "MiqS3Session" |
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 don't think it's a good idea to alter this hash to include non-database related fields. This is passed into the code to actually connect to the database which expects these to all be valid options to pg_basebackup
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.
Well, our choice is this or setting a global @session variable. I'm cool with either.
lib/evm_database_ops.rb
Outdated
end | ||
|
||
block_result = yield db_opts if block_given? | ||
uri || block_result | ||
ensure | ||
session.disconnect if session | ||
session&.disconnect |
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.
Why not just leave this as is?
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.
Can do. Previously there was another line that we moved that called the add method as well.
lib/evm_database_ops.rb
Outdated
@@ -116,13 +118,15 @@ def self.restore(db_opts, connect_opts = {}) | |||
end | |||
|
|||
session = MiqGenericMountSession.new_session(connect_opts) | |||
db_opts[:local_file] = session.uri_to_local_path(uri) | |||
db_opts[:local_file] = session.uri_to_local_path(uri) | |||
db_opts[:object_file] = session.uri_to_object_path(uri) if connect_opts[:object_store] |
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.
If you're using the session object to get this value, why pass it in separately to #add
above? Couldn't the session object internally make the same call?
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.
c16c657
to
5250916
Compare
@miq-bot remove_label wip |
Note that the "supports_objects?" failures are dependent upon ManageIQ/manageiq-gems-pending#357 being merged. Working on the other failures... |
self.class.raw_connect(username, password, service, region) | ||
end | ||
|
||
def with_depot_connection(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.
Is this mixin specific to depots? If not, where else will it be used?
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.
Right now, yes. The code was modified from the code used for the provider but obviously we could not reference provider-specific code in the main repo. Additionally I needed some changes (and reductions) in functionality.
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.
Okay, that makes more sense.
If this is really only meant for the FileDepotS3
class then I think we should probably hide and simplify this even more rather than making it a first-class mixin.
# Connections | ||
# | ||
def connect(options = {}) | ||
username = options[:username] || authentication_userid(options[:auth_type]) |
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.
These are defined in the AuthenticationMixin
which happens to be included in FileDepot
, but if any other class were to include AmazonS3AuthenticationMixin
this would fail.
It feels like this file should be a part of the FileDepotS3
class or maybe a utility in /lib
for wrapping S3 connections in easier to use methods.
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.
So you'd prefer it if I pulled this out and included it in the FileDepotS3 class. I can do that.
true | ||
end | ||
|
||
module ClassMethods |
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.
Why did you choose to implement these as class methods? I'm not sure I see the advantage.
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.
Again - refer to the previous comment - pulled out of the AWS provider... I didn't give it that much original thought - the authentication was already working for the provider....
lib/evm_database_ops.rb
Outdated
end | ||
|
||
session = MiqGenericMountSession.new_session(connect_opts) | ||
db_opts[:local_file] = session.uri_to_local_path(uri) | ||
object_session = session if session.class.name == "MiqS3Session" |
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.
Why not just yield session
unconditionally? It can't hurt to provide the mount session for the other types too, right? Then we can remove the extra object_session
variable entirely.
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.
Because the "add" method on the session for backup (and the "download" method on the session for the restore in a separate PR I need to push out from my stash) are only invoked in EvmDatabaseOps if that session variable was yielded.
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 think it would be more clear to only call add if the session responds to it or check the class when calling the method rather than conditionally yielding. That way the behavior is clear from the .backup
method rather than having to figure out the cases when the session may not have been yielded.
lib/evm_database_ops.rb
Outdated
@@ -112,17 +114,18 @@ def self.restore(db_opts, connect_opts = {}) | |||
else | |||
connect_opts[:remote_file_name] ||= File.basename(backup_file_name(action)) | |||
backup_folder = action == :dump ? "db_dump" : "db_backup" | |||
uri = File.join(connect_opts[:uri], backup_folder, connect_opts[:remote_file_name]) | |||
connect_opts[:uri] = uri = File.join(connect_opts[:uri], backup_folder, connect_opts[:remote_file_name]) |
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.
Will this matter for the other mount types as we can't mount a file path? We were not previously changing the value of connection_opts[:uri]
in the caller here.
Can you ensure that you re-test this with an NFS backup to be sure we don't regress?
3e830fd
to
d8ea1dd
Compare
app/models/file_depot_s3.rb
Outdated
new(:uri => settings[:uri]).verify_credentials(nil, settings.slice(:username, :password)) | ||
end | ||
|
||
extend ActiveSupport::Concern |
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.
Why is this needed?
app/models/file_depot_s3.rb
Outdated
end | ||
|
||
def self.validate_settings(settings) | ||
_log.debug("Validating settings [#{settings}]") |
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.
Will this print the password into the logs? I don't think we want to do that.
app/models/file_depot_s3.rb
Outdated
# | ||
# Connections | ||
# | ||
def raw_connect(access_key_id, secret_access_key, service, aws_region) |
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.
It looks like this is only being used once, can we just move this content into the #connect
method?
app/models/file_depot_s3.rb
Outdated
:log_formatter => Aws::Log::Formatter.new(Aws::Log::Formatter.default.pattern.chomp) | ||
) | ||
|
||
connection |
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 and the assignment above are not necessary.
app/models/file_depot_s3.rb
Outdated
def connect(options = {}) | ||
username = options[:username] || authentication_userid(options[:auth_type]) | ||
password = options[:password] || authentication_password(options[:auth_type]) | ||
service = :S3 |
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.
If we are hard-coding the service, can we use Aws::S3::Resource.new
rather than passing this around and doing a const_get
?
lib/evm_database_ops.rb
Outdated
end | ||
|
||
session = MiqGenericMountSession.new_session(connect_opts) | ||
db_opts[:local_file] = session.uri_to_local_path(uri) | ||
object_session = session if session.class.name == "MiqS3Session" |
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 think it would be more clear to only call add if the session responds to it or check the class when calling the method rather than conditionally yielding. That way the behavior is clear from the .backup
method rather than having to figure out the cases when the session may not have been yielded.
Changes to EvmDatabaseOps for PR ManageIQ/manageiq#17689 will require that the session add method be called on all sessions. This allows us to fail gracefully.
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.
Looks good to me.
Can we also squash the commits? I'm not sure how many of those are really useful to have in the history.
Changes required for DB backups. Works with manageiq-gems-pending PR ManageIQ#355. UI changes still required.
4a5a06b
to
81bb921
Compare
Checked commit jerryk55@81bb921 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0 app/models/file_depot_s3.rb
spec/lib/evm_database_ops_spec.rb
|
Changes required for DB backups. Works with manageiq-gems-pending PR #355.
UI changes still required.
@roliveri @carbonin please review as much as possible although it is still WIP based on some probably changes required for the validation and UI.
This PR is one of several required for https://bugzilla.redhat.com/show_bug.cgi?id=1513520