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

DB Backups to AWS S3 #17689

Merged
merged 1 commit into from
Aug 1, 2018
Merged

Conversation

jerryk55
Copy link
Member

@jerryk55 jerryk55 commented Jul 10, 2018

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

@jerryk55
Copy link
Member Author

@miq-bot add_label WIP

@miq-bot miq-bot added the wip label Jul 10, 2018
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"
Copy link
Member

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.

@@ -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?
Copy link
Member

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?

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
Copy link
Member

Choose a reason for hiding this comment

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

Why was this changed?

Copy link
Member Author

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.

private

def create_bucket(directory_path)
if (ssa_bucket = s3.bucket(directory_path)).exists?
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Absolutely correct.

base_path.join(file.destination_directory)
end

def base_path
Copy link
Member

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.

Copy link
Member Author

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.

@@ -0,0 +1,54 @@
class FileDepotS3 < FileDepot
include AmazonAuthenticationMixin
Copy link
Member

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?

Copy link
Member Author

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.

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"
Copy link
Member

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

Copy link
Member Author

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.

end

block_result = yield db_opts if block_given?
uri || block_result
ensure
session.disconnect if session
session&.disconnect
Copy link
Member

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?

Copy link
Member Author

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.

@@ -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]
Copy link
Member

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?

Copy link
Member

Choose a reason for hiding this comment

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

@jerryk55
Copy link
Member Author

@miq-bot remove_label wip

@miq-bot miq-bot changed the title [WIP] DB Backups to AWS S3 DB Backups to AWS S3 Jul 26, 2018
@miq-bot miq-bot removed the wip label Jul 26, 2018
@jerryk55
Copy link
Member Author

@carbonin @roliveri PLease review. WIP label removed.

@jerryk55
Copy link
Member Author

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 = {})
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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])
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member Author

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....

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"
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

@@ -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])
Copy link
Member

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?

@jerryk55 jerryk55 force-pushed the object_store_db_backups branch from 3e830fd to d8ea1dd Compare July 30, 2018 16:59
new(:uri => settings[:uri]).verify_credentials(nil, settings.slice(:username, :password))
end

extend ActiveSupport::Concern
Copy link
Member

Choose a reason for hiding this comment

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

Why is this needed?

end

def self.validate_settings(settings)
_log.debug("Validating settings [#{settings}]")
Copy link
Member

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.

#
# Connections
#
def raw_connect(access_key_id, secret_access_key, service, aws_region)
Copy link
Member

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?

:log_formatter => Aws::Log::Formatter.new(Aws::Log::Formatter.default.pattern.chomp)
)

connection
Copy link
Member

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.

def connect(options = {})
username = options[:username] || authentication_userid(options[:auth_type])
password = options[:password] || authentication_password(options[:auth_type])
service = :S3
Copy link
Member

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?

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"
Copy link
Member

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.

jerryk55 added a commit to jerryk55/manageiq-gems-pending that referenced this pull request Aug 1, 2018
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.
Copy link
Member

@carbonin carbonin left a 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.
@jerryk55 jerryk55 force-pushed the object_store_db_backups branch from 4a5a06b to 81bb921 Compare August 1, 2018 18:53
@miq-bot
Copy link
Member

miq-bot commented Aug 1, 2018

Checked commit jerryk55@81bb921 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0
5 files checked, 2 offenses detected

app/models/file_depot_s3.rb

spec/lib/evm_database_ops_spec.rb

@carbonin carbonin self-assigned this Aug 1, 2018
@carbonin carbonin merged commit be4c07e into ManageIQ:master Aug 1, 2018
@carbonin carbonin added this to the Sprint 92 Ending Aug 13, 2018 milestone Aug 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants