cookbooks/aws-parallelcluster-environment/spec/unit/resources/efs_spec.rb (363 lines of code) (raw):
require 'spec_helper'
class ConvergeEfs
def self.install_utils(chef_run, efs_utils_version:, tarball_checksum:)
chef_run.converge_dsl('aws-parallelcluster-environment') do
efs 'install_utils' do
efs_utils_checksum tarball_checksum
efs_utils_version efs_utils_version
action :install_utils
end
end
end
end
def mock_get_package_version(package, expected_version)
stubs_for_resource('efs') do |res|
allow(res).to receive(:get_package_version).with(package).and_return(expected_version)
end
end
def mock_already_installed(package, expected_version, installed)
stubs_for_resource('efs') do |res|
allow(res).to receive(:already_installed?).with(package, expected_version).and_return(installed)
end
end
describe 'efs:install_utils' do
context "on amazon2" do
cached(:efs_utils_version) { '1.2.3' }
cached(:tarball_checksum) { 'tarball_checksum' }
let(:chef_run) do
runner(platform: 'amazon', version: '2', step_into: ['efs'])
end
context "when same version of amazon-efs-utils already installed" do
before do
mock_get_package_version('amazon-efs-utils', efs_utils_version)
ConvergeEfs.install_utils(chef_run, efs_utils_version: efs_utils_version, tarball_checksum: tarball_checksum)
end
it 'does not install amazon-efs-utils' do
is_expected.not_to install_package('amazon-efs-utils')
end
end
context "when newer version of amazon-efs-utils already installed" do
before do
mock_get_package_version('amazon-efs-utils', '1.3.2')
ConvergeEfs.install_utils(chef_run, efs_utils_version: efs_utils_version, tarball_checksum: tarball_checksum)
end
it 'does not install amazon-efs-utils' do
is_expected.not_to install_package('amazon-efs-utils')
end
end
context "when amazon-efs-utils not installed" do
before do
mock_get_package_version('amazon-efs-utils', '')
ConvergeEfs.install_utils(chef_run, efs_utils_version: efs_utils_version, tarball_checksum: tarball_checksum)
end
it 'installs amazon-efs-utils' do
is_expected.to install_utils_efs('install_utils')
is_expected.to install_package('amazon-efs-utils-1.2.3').with(retries: 3).with(retry_delay: 5)
end
end
context "when older version of amazon-efs-utils installed" do
before do
mock_get_package_version('amazon-efs-utils', '1.1.4')
ConvergeEfs.install_utils(chef_run, efs_utils_version: efs_utils_version, tarball_checksum: tarball_checksum)
end
it 'installs amazon-efs-utils' do
is_expected.to install_package('amazon-efs-utils-1.2.3').with(retries: 3).with(retry_delay: 5)
end
end
end
for_oses([
%w(ubuntu 24.04),
%w(ubuntu 22.04),
]) do |platform, version|
context "on #{platform}#{version}" do
cached(:source_dir) { 'SOURCE DIR' }
cached(:utils_version) { '1.2.3' }
cached(:tarball_path) { "#{source_dir}/efs-utils-#{utils_version}.tar.gz" }
cached(:tarball_url) { "https://#{aws_region}-aws-parallelcluster.s3.#{aws_region}.test_aws_domain/archives/dependencies/efs/v#{utils_version}.tar.gz" }
cached(:tarball_checksum) { 'TARBALL CHECKSUM' }
cached(:bash_code) do
<<-EFSUTILSINSTALL
set -e
tar xf #{tarball_path}
cd efs-utils-#{utils_version}
./build-deb.sh
apt-get -y install ./build/amazon-efs-utils*deb
EFSUTILSINSTALL
end
context "utils package not yet installed" do
cached(:chef_run) do
mock_already_installed('amazon-efs-utils', utils_version, false)
runner = runner(platform: platform, version: version, step_into: ['efs']) do |node|
node.override['cluster']['efs_utils']['tarball_path'] = tarball_path
node.override['cluster']['sources_dir'] = source_dir
node.override['cluster']['region'] = aws_region
end
ConvergeEfs.install_utils(runner, efs_utils_version: utils_version, tarball_checksum: tarball_checksum)
end
cached(:node) { chef_run.node }
it 'creates sources dir' do
is_expected.to create_directory(source_dir).with_recursive(true)
end
it 'updates package repos' do
is_expected.to update_package_repos('update package repositories')
end
it 'downloads tarball' do
is_expected.to create_if_missing_remote_file(tarball_path)
.with(source: tarball_url)
.with(mode: '0644')
.with(retries: 3)
.with(retry_delay: 5)
.with(checksum: tarball_checksum)
end
it 'installs package from downloaded tarball' do
is_expected.to run_bash('install efs utils')
.with(cwd: source_dir)
.with(code: bash_code)
end
end
context "utils package already installed" do
cached(:chef_run) do
mock_already_installed('amazon-efs-utils', utils_version, true)
runner = runner(platform: platform, version: version, step_into: ['efs']) do |node|
node.override['cluster']['efs_utils']['tarball_path'] = tarball_path
node.override['cluster']['sources_dir'] = source_dir
end
ConvergeEfs.install_utils(runner, efs_utils_version: utils_version, tarball_checksum: tarball_checksum)
end
cached(:node) { chef_run.node }
it 'does not download tarball' do
is_expected.not_to create_if_missing_remote_file(tarball_path)
end
it 'does not install package from downloaded tarball' do
is_expected.not_to run_bash('install efs utils')
end
end
end
end
for_oses([
%w(redhat 8),
%w(rocky 8),
%w(redhat 9),
%w(rocky 9),
]) do |platform, version|
context "on #{platform}#{version}" do
cached(:source_dir) { 'SOURCE DIR' }
cached(:utils_version) { '1.2.3' }
cached(:tarball_path) { "#{source_dir}/efs-utils-#{utils_version}.tar.gz" }
cached(:tarball_url) { "https://#{aws_region}-aws-parallelcluster.s3.#{aws_region}.test_aws_domain/archives/dependencies/efs/v#{utils_version}.tar.gz" }
cached(:tarball_checksum) { 'TARBALL CHECKSUM' }
cached(:bash_code) do
<<-EFSUTILSINSTALL
set -e
tar xf #{tarball_path}
cd efs-utils-#{utils_version}
make rpm
yum -y install ./build/amazon-efs-utils*rpm
EFSUTILSINSTALL
end
cached(:required_packages) do
{
"redhat" => %w(rpm-build make rust cargo openssl-devel),
"rocky" => %w(rpm-build make rust cargo openssl-devel),
}
end
context "utils package not yet installed" do
cached(:chef_run) do
mock_already_installed('amazon-efs-utils', utils_version, false)
runner = runner(platform: platform, version: version, step_into: ['efs']) do |node|
node.override['cluster']['efs_utils']['tarball_path'] = tarball_path
node.override['cluster']['sources_dir'] = source_dir
node.override['cluster']['region'] = aws_region
end
ConvergeEfs.install_utils(runner, efs_utils_version: utils_version, tarball_checksum: tarball_checksum)
end
it 'creates sources dir' do
is_expected.to create_directory(source_dir).with_recursive(true)
end
it 'updates package repos' do
is_expected.to update_package_repos('update package repositories')
end
it 'installs prerequisites' do
is_expected.to install_package(required_packages[platform])
.with(retries: 3)
.with(retry_delay: 5)
end
it 'downloads tarball' do
is_expected.to create_if_missing_remote_file(tarball_path)
.with(source: tarball_url)
.with(mode: '0644')
.with(retries: 3)
.with(retry_delay: 5)
.with(checksum: tarball_checksum)
end
it 'installs package from downloaded tarball' do
is_expected.to run_bash('install efs utils')
.with(cwd: source_dir)
.with(code: bash_code)
end
end
context "utils package already installed" do
cached(:chef_run) do
mock_already_installed('amazon-efs-utils', utils_version, true)
runner = runner(platform: platform, version: version, step_into: ['efs']) do |node|
node.override['cluster']['efs_utils']['tarball_path'] = tarball_path
node.override['cluster']['sources_dir'] = source_dir
end
ConvergeEfs.install_utils(runner, efs_utils_version: utils_version, tarball_checksum: tarball_checksum)
end
it 'does not download tarball' do
is_expected.not_to create_if_missing_remote_file(tarball_path)
end
it 'does not install package from downloaded tarball' do
is_expected.not_to run_bash('install efs utils')
end
end
end
end
end
describe 'efs:mount' do
for_all_oses do |platform, version|
%w(HeadNode ComputeFleet).each do |node_type|
context "on #{platform}#{version} and node type #{node_type}" do
cached(:chef_run) do
runner = runner(platform: platform, version: version, step_into: ['efs']) do |node|
node.override['cluster']['region'] = "REGION"
node.override['cluster']['aws_domain'] = "DOMAIN"
node.override['cluster']['node_type'] = node_type
end
runner.converge_dsl do
efs 'mount' do
efs_fs_id_array %w(id_1 id_2 id_3 id_4)
shared_dir_array %w(shared_dir_1 /shared_dir_2 /shared_dir_3 /shared_dir_4)
efs_encryption_in_transit_array %w(true true not_true true)
efs_iam_authorization_array %w(not_true true true true)
efs_access_point_id_array %w(none none none ap)
action :mount
end
end
end
before do
stub_command("mount | grep ' /shared_dir_1 '").and_return(false)
stub_command("mount | grep ' /shared_dir_2 '").and_return(true)
stub_command("mount | grep ' /shared_dir_3 '").and_return(true)
stub_command("mount | grep ' /shared_dir_4 '").and_return(false)
end
it 'mounts efs' do
is_expected.to mount_efs('mount')
end
it 'creates shared directory' do
%w(/shared_dir_1 /shared_dir_2 /shared_dir_3 /shared_dir_4).each do |shared_dir|
is_expected.to create_directory(shared_dir)
.with(owner: 'root')
.with(group: 'root')
.with(mode: '1777')
# .with(recursive: true) # even if we set recursive a true, the test fails
end
end
it 'mounts shared dir if not already mounted' do
is_expected.to mount_mount('/shared_dir_1')
.with(device: 'id_1:/')
.with(fstype: 'efs')
.with(dump: 0)
.with(pass: 0)
.with(options: %w(_netdev noresvport tls))
.with(retries: 10)
.with(retry_delay: 60)
is_expected.to mount_mount('/shared_dir_4')
.with(device: 'id_4:/')
.with(fstype: 'efs')
.with(dump: 0)
.with(pass: 0)
.with(options: %w(_netdev noresvport tls iam accesspoint=ap))
.with(retries: 10)
.with(retry_delay: 60)
end
it 'enables shared dir mount if already mounted' do
is_expected.to enable_mount('/shared_dir_2')
.with(device: 'id_2:/')
.with(fstype: 'efs')
.with(dump: 0)
.with(pass: 0)
.with(options: %w(_netdev noresvport tls iam))
.with(retries: 10)
.with(retry_delay: 6)
is_expected.to enable_mount('/shared_dir_3')
.with(device: 'id_3:/')
.with(fstype: 'efs')
.with(dump: 0)
.with(pass: 0)
.with(options: %w(_netdev noresvport))
.with(retries: 10)
.with(retry_delay: 6)
end
if node_type == "HeadNode"
it 'changes permissions' do
%w(/shared_dir_1 /shared_dir_2 /shared_dir_3).each do |shared_dir|
is_expected.to create_directory("change permissions for #{shared_dir}")
.with(path: shared_dir)
.with(owner: 'root')
.with(group: 'root')
.with(mode: '1777')
end
end
end
end
end
end
end
describe 'efs:unmount' do
for_all_oses do |platform, version|
context "on #{platform}#{version}" do
cached(:chef_run) do
runner = runner(platform: platform, version: version, step_into: ['efs']) do |node|
node.override['cluster']['region'] = "REGION"
node.override['cluster']['aws_domain'] = "DOMAIN"
end
runner.converge_dsl do
efs 'unmount' do
efs_fs_id_array %w(id_1 id_2)
shared_dir_array %w(shared_dir_1 /shared_dir_2)
action :unmount
end
end
end
before do
stub_command("mount | grep ' /shared_dir_1 '").and_return(false)
stub_command("mount | grep ' /shared_dir_2 '").and_return(true)
allow(Dir).to receive(:exist?).with("/shared_dir_1").and_return(true)
allow(Dir).to receive(:empty?).with("/shared_dir_1").and_return(true)
allow(Dir).to receive(:exist?).with("/shared_dir_2").and_return(true)
allow(Dir).to receive(:empty?).with("/shared_dir_2").and_return(false)
end
it 'unmounts efs' do
is_expected.to unmount_efs('unmount')
end
it 'checks active processes' do
is_expected.to check_active_processes_file_utils('check active processes on /shared_dir_1')
.with(file: '/shared_dir_1')
is_expected.to check_active_processes_file_utils('check active processes on /shared_dir_2')
.with(file: '/shared_dir_2')
end
it 'unmounts efs only if mounted' do
is_expected.not_to run_execute('unmount efs')
.with(command: 'umount -fl /shared_dir_1')
is_expected.to run_execute('unmount efs')
.with(command: "umount -fl /shared_dir_2")
.with(retries: 10)
.with(retry_delay: 6)
.with(timeout: 60)
end
%w(/shared_dir_1 /shared_dir_2).each do |shared_dir|
it "removes volume #{shared_dir} from /etc/fstab" do
is_expected.to edit_delete_lines("remove volume #{shared_dir} from /etc/fstab")
.with(path: "/etc/fstab")
.with(pattern: " #{shared_dir} ")
end
end
it "deletes shared dir only if it exists and it is empty" do
is_expected.to delete_directory('/shared_dir_1')
.with(recursive: false)
is_expected.not_to delete_directory('/shared_dir_2')
end
end
end
end