Deployment artifacts management for upgrades:

* update packages repository source(ubuntu && centos);
* support custom source for manifests and modules;
* tests.

Implements: blueprint fuel-upgrade
Change-Id: Id2d51049a79095c1dd92599dd360218ee9926e69
This commit is contained in:
Dmitry Ilyin 2014-04-14 19:36:44 +04:00
parent 401bc474b1
commit 0cb0b26773
3 changed files with 334 additions and 15 deletions

View File

@ -1 +1 @@
1.9.3
2.1

View File

@ -14,6 +14,7 @@
require 'fileutils'
require 'popen4'
require 'uri'
KEY_DIR = "/var/lib/astute"
@ -43,6 +44,9 @@ module Astute
# Will be used by puppet after to connect nodes between themselves.
upload_ssh_keys(part.map{ |n| n['uid'] }, part.first['deployment_id'])
# Update packages source list
update_repo_sources(part) if part.first['repo_source']
# Sync puppet manifests and modules to every node (emulate puppet master)
sync_puppet_manifests(part)
@ -110,11 +114,31 @@ module Astute
def sync_puppet_manifests(deployment_info)
sync_mclient = MClient.new(@ctx, "puppetsync", deployment_info.map{ |n| n['uid'] }.uniq)
master_ip = deployment_info.first['master_ip']
# Paths /puppet/modules and /puppet/manifests/ in master node set by FUEL
modules_source = deployment_info.first['puppet_modules_source'] || "rsync://#{master_ip}:/puppet/modules/"
manifests_source = deployment_info.first['puppet_manifests_source'] || "rsync://#{master_ip}:/puppet/manifests/"
# Paths to Puppet modules and manifests at the master node set by Nailgun
# Check fuel source code /deployment/puppet/nailgun/manifests/puppetsync.pp
sync_mclient.rsync(:modules_source => "rsync://#{master_ip}:/puppet/modules/",
:manifests_source => "rsync://#{master_ip}:/puppet/manifests/"
)
schemas = [modules_source, manifests_source].map do |url|
begin
URI.parse(url).scheme
rescue URI::InvalidURIError => e
raise DeploymentEngineError, e.message
end
end
if schemas.select{ |x| x != schemas.first }.present?
raise DeploymentEngineError, "Scheme for puppet_modules_source '#{schemas.first}' and" \
" puppet_manifests_source '#{schemas.last}' not equivalent!"
end
case schemas.first
when 'rsync'
sync_mclient.rsync(:modules_source => modules_source,
:manifests_source => manifests_source
)
else
raise DeploymentEngineError, "Unknown scheme '#{schemas.first}' in #{modules_source}"
end
end
def generate_ssh_keys(deployment_id, overwrite=false)
@ -159,12 +183,91 @@ module Astute
end
end
def update_repo_sources(deployment_info)
content = generate_repo_source(deployment_info)
upload_repo_source(deployment_info, content)
regenerate_metadata(deployment_info)
end
def generate_repo_source(deployment_info)
ubuntu_source = -> (name, url) { "deb #{url}" }
centos_source = -> (name, url) do
["[#{name.downcase}]", "name=#{name}", "baseurl=#{url}", "gpgcheck=0"].join("\n")
end
formatter = case target_os(deployment_info)
when 'centos' then centos_source
when 'ubuntu' then ubuntu_source
end
content = []
# FIXME: This key 'repo_source' not approved
deployment_info.first['repo_source'].each do |name, url|
content << formatter.call(name,url)
end
content.join("\n")
end
def upload_repo_source(deployment_info, content)
upload_mclient = MClient.new(@ctx, "uploadfile", deployment_info.map{ |n| n['uid'] }.uniq)
destination_path = case target_os(deployment_info)
when 'centos' then '/etc/yum.repos.d/nailgun.repo'
when 'ubuntu' then '/etc/apt/sources.list'
end
upload_mclient.upload(:path => destination_path,
:content => content,
:user_owner => 'root',
:group_owner => 'root',
:permissions => '0644',
:dir_permissions => '0755',
:overwrite => true,
:parents => true
)
end
def regenerate_metadata(deployment_info)
cmd = case target_os(deployment_info)
when 'centos' then "yum clean all"
when 'ubuntu' then "apt-get clean; apt-get update"
end
run_shell_command_remotely(deployment_info.map{ |n| n['uid'] }.uniq, cmd)
end
def target_os(deployment_info)
os = deployment_info.first['cobbler']['profile']
case os
when 'centos-x86_64' then 'centos'
when 'rhel-x86_64' then 'centos'
when 'ubuntu_1204_x86_64' then 'ubuntu'
else
raise DeploymentEngineError, "Unknown system #{os}"
end
end
def run_system_command(cmd)
pid, _, stdout, stderr = Open4::popen4 cmd
_, status = Process::waitpid2 pid
return status.exitstatus, stdout, stderr
end
def run_shell_command_remotely(node_uids, cmd)
shell = MClient.new(@ctx,
'execute_shell_command',
node_uids,
check_result=true,
timeout=60,
retries=1)
#TODO: return result for all nodes not only for first
response = shell.execute(:cmd => cmd).first
Astute.logger.debug("#{@ctx.task_id}: cmd: #{cmd}
stdout: #{response[:data][:stdout]}
stderr: #{response[:data][:stderr]}
exit code: #{response[:data][:exit_code]}")
response
end
def enable_puppet_deploy(node_uids)
puppetd = MClient.new(@ctx, "puppetd", node_uids)
puppetd.enable

View File

@ -41,11 +41,12 @@ describe Astute::DeploymentEngine do
deployer.stubs(:upload_ssh_keys)
deployer.stubs(:sync_puppet_manifests)
deployer.stubs(:enable_puppet_deploy)
deployer.stubs(:update_repo_sources)
deployer.stubs(:deploy_piece)
end
it 'should generate and upload ssh keys' do
nodes = [{'uid' => 1, 'deployment_id' => 1}, {'uid' => 2}, {'uid' => 1}]
deployer.stubs(:deploy_piece)
deployer.expects(:generate_ssh_keys).with(nodes.first['deployment_id'])
deployer.expects(:upload_ssh_keys).with([1,2], nodes.first['deployment_id']).returns()
@ -54,9 +55,27 @@ describe Astute::DeploymentEngine do
deployer.deploy(nodes)
end
it 'should setup packages repositories' do
nodes = [
{'uid' => 1,
'deployment_id' => 1,
'repo_source' => {
'Nailgun' => 'http://10.20.0.2:8080/centos/fuelweb/x86_64/'
}
},
{'uid' => 2},
{'uid' => 1}
]
uniq_nodes = nodes[0..-2]
deployer.expects(:update_repo_sources).with(uniq_nodes)
deployer.deploy(nodes)
end
it 'should enable puppet for all nodes' do
nodes = [{'uid' => 1, 'deployment_id' => 1}, {'uid' => 2}, {'uid' => 1}]
deployer.stubs(:deploy_piece)
deployer.expects(:enable_puppet_deploy).with([1,2]).returns()
@ -157,22 +176,221 @@ describe Astute::DeploymentEngine do
deployer.stubs(:enable_puppet_deploy)
end
let(:nodes) { [{'uid' => 1, 'deployment_id' => 1, 'master_ip' => '10.20.0.2'}, {'uid' => 2}] }
it "should sync puppet modules and manifests mcollective client 'puppetsync'" do
let(:nodes) { [
{'uid' => 1,
'deployment_id' => 1,
'master_ip' => '10.20.0.2',
'puppet_modules_source' => 'rsync://10.20.0.2:/puppet/modules/',
'puppet_manifests_source' => 'rsync://10.20.0.2:/puppet/manifests/'
},
{'uid' => 2}
]
}
let(:mclient) do
mclient = mock_rpcclient(nodes)
Astute::MClient.any_instance.stubs(:rpcclient).returns(mclient)
Astute::MClient.any_instance.stubs(:log_result).returns(mclient)
Astute::MClient.any_instance.stubs(:check_results_with_retries).returns(mclient)
mclient
end
let(:master_ip) { nodes.first['master_ip'] }
master_ip = nodes.first['master_ip']
mclient.expects(:rsync).with(:modules_source => "rsync://#{master_ip}:/puppet/modules/",
:manifests_source => "rsync://#{master_ip}:/puppet/manifests/"
it "should sync puppet modules and manifests mcollective client 'puppetsync'" do
mclient.expects(:rsync).with(:modules_source => "rsync://10.20.0.2:/puppet/modules/",
:manifests_source => "rsync://10.20.0.2:/puppet/manifests/"
)
deployer.deploy(nodes)
end
it 'should able to customize path for puppet modules and manifests' do
modules_source = 'rsync://10.20.0.2:/puppet/vX/modules/'
manifests_source = 'rsync://10.20.0.2:/puppet/vX/manifests/'
nodes.first['puppet_modules_source'] = modules_source
nodes.first['puppet_manifests_source'] = manifests_source
mclient.expects(:rsync).with(:modules_source => modules_source,
:manifests_source => manifests_source
)
deployer.deploy(nodes)
end
it 'should raise exception if modules/manifests schema of uri is not equal' do
nodes.first['puppet_manifests_source'] = 'rsync://10.20.0.2:/puppet/vX/modules/'
nodes.first['puppet_manifests_source'] = 'http://10.20.0.2:/puppet/vX/manifests/'
mclient.expects(:rsync).never
expect { deployer.deploy(nodes) }.to raise_error(Astute::DeploymentEngineError,
/Scheme for puppet_modules_source 'rsync' and puppet_manifests_source/)
end
it 'should raise exception if modules/manifests source uri is incorrect' do
nodes.first['puppet_manifests_source'] = ':/puppet/modules/'
mclient.expects(:rsync).never
expect { deployer.deploy(nodes) }.to raise_error(Astute::DeploymentEngineError,
/bad URI/)
end
it 'should raise exception if schema of uri is incorrect' do
nodes.first['puppet_modules_source'] = 'http2://localhost/puppet/modules/'
nodes.first['puppet_manifests_source'] = 'http2://localhost/puppet/manifests/'
mclient.expects(:rsync).never
expect { deployer.deploy(nodes) }.to raise_error(Astute::DeploymentEngineError,
/Unknown scheme /)
end
end
describe '#update_repo_sources' do
before(:each) do
deployer.stubs(:generate_ssh_keys)
deployer.stubs(:upload_ssh_keys)
deployer.stubs(:sync_puppet_manifests)
deployer.stubs(:enable_puppet_deploy)
deployer.stubs(:deploy_piece)
end
let(:nodes) do
[
{'uid' => 1,
'deployment_id' => 1,
'cobbler' => {
'profile' => 'centos-x86_64'
},
'repo_source' => {
'Nailgun' => 'http://10.20.0.2:8080/centos/fuelweb/x86_64/',
}
},
{'uid' => 2}
]
end
let(:mclient) do
mclient = mock_rpcclient(nodes)
Astute::MClient.any_instance.stubs(:rpcclient).returns(mclient)
Astute::MClient.any_instance.stubs(:log_result).returns(mclient)
Astute::MClient.any_instance.stubs(:check_results_with_retries).returns(mclient)
mclient
end
context 'source configuration generation' do
before(:each) do
deployer.stubs(:regenerate_metadata)
end
it 'should generate correct config for centos' do
content = ["[nailgun]",
"name=Nailgun",
"baseurl=http://10.20.0.2:8080/centos/fuelweb/x86_64/",
"gpgcheck=0"].join("\n")
deployer.expects(:upload_repo_source).with(nodes, content)
deployer.deploy(nodes)
end
it 'should generate correct config for rhel' do
nodes.first['cobbler']['profile'] = 'rhel-x86_64'
content = ["[nailgun]",
"name=Nailgun",
"baseurl=http://10.20.0.2:8080/centos/fuelweb/x86_64/",
"gpgcheck=0"].join("\n")
deployer.expects(:upload_repo_source).with(nodes, content)
deployer.deploy(nodes)
end
it 'should generate correct config for ubuntu' do
nodes.first['cobbler']['profile'] = 'ubuntu_1204_x86_64'
nodes.first['repo_source']['Nailgun'] =
'http://10.20.0.2:8080/ubuntu/fuelweb/x86_64 precise main'
content = "deb http://10.20.0.2:8080/ubuntu/fuelweb/x86_64 precise main"
deployer.expects(:upload_repo_source).with(nodes, content)
deployer.deploy(nodes)
end
it 'should raise error if os not recognized' do
nodes.first['cobbler']['profile'] = 'unknown'
expect {deployer.deploy(nodes)}.to raise_error(Astute::DeploymentEngineError,
/Unknown system/)
end
end # source configuration generation
context 'new source configuration uploading' do
let(:repo_content) { "repo conf" }
before(:each) do
deployer.stubs(:generate_repo_source).returns(repo_content)
deployer.stubs(:regenerate_metadata)
end
it 'should upload config in correct place for centos' do
mclient.expects(:upload).with(:path => '/etc/yum.repos.d/nailgun.repo',
:content => repo_content,
:user_owner => 'root',
:group_owner => 'root',
:permissions => '0644',
:dir_permissions => '0755',
:overwrite => true,
:parents => true
)
deployer.deploy(nodes)
end
it 'should upload config in correct place for rhel' do
nodes.first['cobbler']['profile'] = 'rhel-x86_64'
mclient.expects(:upload).with(:path => '/etc/yum.repos.d/nailgun.repo',
:content => repo_content,
:user_owner => 'root',
:group_owner => 'root',
:permissions => '0644',
:dir_permissions => '0755',
:overwrite => true,
:parents => true
)
deployer.deploy(nodes)
end
it 'should upload config in correct place for ubuntu' do
nodes.first['cobbler']['profile'] = 'ubuntu_1204_x86_64'
mclient.expects(:upload).with(:path => '/etc/apt/sources.list',
:content => repo_content,
:user_owner => 'root',
:group_owner => 'root',
:permissions => '0644',
:dir_permissions => '0755',
:overwrite => true,
:parents => true
)
deployer.deploy(nodes)
end
end #new source configuration uploading
context 'metadata regeneration' do
before(:each) do
deployer.stubs(:generate_repo_source)
deployer.stubs(:upload_repo_source)
end
it 'should regenerate metadata for centos' do
mclient.expects(:execute).with(:cmd => 'yum clean all').returns([{:data => {} }])
deployer.deploy(nodes)
end
it 'should regenerate metadata for rhel' do
nodes.first['cobbler']['profile'] = 'rhel-x86_64'
mclient.expects(:execute).with(:cmd => 'yum clean all').returns([{:data => {} }])
deployer.deploy(nodes)
end
it 'should regenerate metadata for ubuntu' do
nodes.first['cobbler']['profile'] = 'ubuntu_1204_x86_64'
mclient.expects(:execute).with(:cmd => 'apt-get clean; apt-get update').returns([{:data => {} }])
deployer.deploy(nodes)
end
end #'metadata regeneration'
end # update_repo_sources
describe '#generation and uploading of ssh keys' do
before(:each) do
Astute.config.PUPPET_SSH_KEYS = ['nova']
@ -275,7 +493,6 @@ describe Astute::DeploymentEngine do
deployer.deploy(nodes)
end
end # end context
context 'upload ssh keys' do
@ -311,6 +528,5 @@ describe Astute::DeploymentEngine do
deployer.deploy(nodes)
end
end # context
end # describe
end