264 lines
11 KiB
Ruby
264 lines
11 KiB
Ruby
# Copyright 2013 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
require 'json'
|
|
require 'timeout'
|
|
|
|
module Astute
|
|
class DeploymentEngine
|
|
def initialize(context)
|
|
if self.class.superclass.name == 'Object'
|
|
raise "Instantiation of this superclass is not allowed. Please subclass from #{self.class.name}."
|
|
end
|
|
@ctx = context
|
|
end
|
|
|
|
def deploy(nodes, attrs)
|
|
# See implementation in subclasses, this may be everriden
|
|
attrs['deployment_mode'] ||= 'multinode' # simple multinode deployment is the default
|
|
attrs['use_cinder'] ||= nodes.any?{|n| n['role'] == 'cinder'}
|
|
@ctx.deploy_log_parser.deploy_type = attrs['deployment_mode']
|
|
Astute.logger.info "Deployment mode #{attrs['deployment_mode']}"
|
|
self.send("deploy_#{attrs['deployment_mode']}", nodes, attrs)
|
|
end
|
|
|
|
def method_missing(method, *args)
|
|
Astute.logger.error "Method #{method} is not implemented for #{self.class}, raising exception."
|
|
raise "Method #{method} is not implemented for #{self.class}"
|
|
end
|
|
|
|
# we mix all attrs and prepare them for Puppet
|
|
# Works for multinode deployment mode
|
|
def attrs_multinode(nodes, attrs)
|
|
attrs['nodes'] = nodes.map do |n|
|
|
{
|
|
'fqdn' => n['fqdn'],
|
|
'name' => n['fqdn'].split(/\./)[0],
|
|
'role' => n['role'],
|
|
'internal_address' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'management')}[0]['ip'].split(/\//)[0],
|
|
'internal_br' => n['internal_br'],
|
|
'internal_netmask' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'management')}[0]['netmask'],
|
|
'storage_address' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'storage')}[0]['ip'].split(/\//)[0],
|
|
'storage_netmask' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'storage')}[0]['netmask'],
|
|
'public_address' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'public')}[0]['ip'].split(/\//)[0],
|
|
'public_br' => n['public_br'],
|
|
'public_netmask' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'public')}[0]['netmask'],
|
|
'default_gateway' => n['default_gateway']
|
|
}
|
|
end
|
|
# TODO(mihgen): we should report error back if there are not enough metadata passed
|
|
attrs
|
|
end
|
|
|
|
# This method is called by Ruby metaprogramming magic from deploy method
|
|
# It should not contain any magic with attributes, and should not directly run any type of MC plugins
|
|
# It does only support of deployment sequence. See deploy_piece implementation in subclasses.
|
|
def deploy_multinode(nodes, attrs)
|
|
ctrl_nodes = nodes.select {|n| n['role'] == 'controller'}
|
|
other_nodes = nodes - ctrl_nodes
|
|
|
|
Astute.logger.info "Starting deployment of primary controller"
|
|
deploy_piece(ctrl_nodes, attrs)
|
|
|
|
Astute.logger.info "Starting deployment of other nodes"
|
|
deploy_piece(other_nodes, attrs)
|
|
|
|
return
|
|
end
|
|
|
|
def attrs_ha(nodes, attrs)
|
|
# we use the same set of mount points for all storage nodes
|
|
attrs['mp'] = [{'point' => '1', 'weight' => '1'},{'point'=>'2','weight'=>'2'}]
|
|
mountpoints = ""
|
|
attrs['mp'].each do |mountpoint|
|
|
mountpoints << "#{mountpoint['point']} #{mountpoint['weight']}\n"
|
|
end
|
|
|
|
Astute.logger.debug("#{nodes}")
|
|
attrs['nodes'] = nodes.map do |n|
|
|
{
|
|
'fqdn' => n['fqdn'],
|
|
'name' => n['fqdn'].split(/\./)[0],
|
|
'role' => n['role'],
|
|
'mountpoints' => mountpoints,
|
|
'internal_address' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'management')}[0]['ip'].split(/\//)[0],
|
|
'internal_br' => n['internal_br'],
|
|
'internal_netmask' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'management')}[0]['netmask'],
|
|
'public_address' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'public')}[0]['ip'].split(/\//)[0],
|
|
'public_br' => n['public_br'],
|
|
'public_netmask' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'public')}[0]['netmask'],
|
|
'swift_zone' => n['id'],
|
|
'storage_address' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'storage')}[0]['ip'].split(/\//)[0],
|
|
'storage_netmask' => n['network_data'].select {|nd| select_ifaces(nd['name'], 'storage')}[0]['netmask'],
|
|
'default_gateway' => n['default_gateway']
|
|
}
|
|
end
|
|
|
|
if attrs['nodes'].select { |node| node['role'] == 'primary-controller' }.empty?
|
|
ctrl_nodes = attrs['nodes'].select {|n| n['role'] == 'controller'}
|
|
ctrl_nodes[0]['role'] = 'primary-controller'
|
|
end
|
|
|
|
attrs
|
|
end
|
|
|
|
alias :attrs_ha_full :attrs_ha
|
|
alias :attrs_ha_compact :attrs_ha
|
|
|
|
def deploy_ha_full(nodes, attrs)
|
|
primary_ctrl_nodes = nodes.select {|n| n['role'] == 'primary-controller'}
|
|
ctrl_nodes = nodes.select {|n| n['role'] == 'controller'}
|
|
compute_nodes = nodes.select {|n| n['role'] == 'compute'}
|
|
quantum_nodes = nodes.select {|n| n['role'] == 'quantum'}
|
|
storage_nodes = nodes.select {|n| n['role'] == 'storage'}
|
|
proxy_nodes = nodes.select {|n| n['role'] == 'swift-proxy'}
|
|
primary_proxy_nodes = nodes.select {|n| n['role'] == 'primary-swift-proxy'}
|
|
other_nodes = nodes - ctrl_nodes - primary_ctrl_nodes - \
|
|
primary_proxy_nodes - quantum_nodes - storage_nodes - proxy_nodes
|
|
|
|
Astute.logger.info "Starting deployment of primary swift proxy"
|
|
deploy_piece(primary_proxy_nodes, attrs)
|
|
|
|
Astute.logger.info "Starting deployment of non-primary swift proxies"
|
|
deploy_piece(proxy_nodes, attrs)
|
|
|
|
Astute.logger.info "Starting deployment of swift storages"
|
|
deploy_piece(storage_nodes, attrs)
|
|
|
|
Astute.logger.info "Starting deployment of primary controller"
|
|
deploy_piece(primary_ctrl_nodes, attrs)
|
|
|
|
Astute.logger.info "Starting deployment of all controllers one by one"
|
|
ctrl_nodes.each {|n| deploy_piece([n], attrs)}
|
|
|
|
Astute.logger.info "Starting deployment of other nodes"
|
|
deploy_piece(other_nodes, attrs)
|
|
return
|
|
end
|
|
|
|
def deploy_ha_compact(nodes, attrs)
|
|
primary_ctrl_nodes = nodes.select {|n| n['role'] == 'primary-controller'}
|
|
ctrl_nodes = nodes.select {|n| n['role'] == 'controller'}
|
|
compute_nodes = nodes.select {|n| n['role'] == 'compute'}
|
|
quantum_nodes = nodes.select {|n| n['role'] == 'quantum'}
|
|
storage_nodes = nodes.select {|n| n['role'] == 'storage'}
|
|
proxy_nodes = nodes.select {|n| n['role'] == 'swift-proxy'}
|
|
primary_proxy_nodes = nodes.select {|n| n['role'] == 'primary-swift-proxy'}
|
|
other_nodes = nodes - ctrl_nodes - primary_ctrl_nodes - \
|
|
primary_proxy_nodes - quantum_nodes
|
|
|
|
#FIXME: add last_controller attribute to attributes hash in order to determine
|
|
#if we are the last controller in deployment sequence and it is safe to
|
|
#upload test virtual machine image
|
|
|
|
last_controller = ctrl_nodes.last
|
|
if last_controller['name']
|
|
attrs['last_controller'] = last_controller['name']
|
|
elsif last_controller['fqdn']
|
|
attrs['last_controller'] = last_controller['fqdn'].split(/\./)[0]
|
|
end
|
|
|
|
Astute.logger.info "Starting deployment of primary controller"
|
|
deploy_piece(primary_ctrl_nodes, attrs)
|
|
|
|
Astute.logger.info "Starting deployment of all controllers one by one"
|
|
ctrl_nodes.each {|n| deploy_piece([n], attrs)}
|
|
|
|
Astute.logger.info "Starting deployment of other nodes"
|
|
deploy_piece(other_nodes, attrs)
|
|
return
|
|
end
|
|
|
|
alias :deploy_ha :deploy_ha_compact
|
|
|
|
def attrs_rpmcache(nodes, attrs)
|
|
attrs
|
|
end
|
|
|
|
def deploy_rpmcache(nodes, attrs)
|
|
Astute.logger.info "Starting release downloading"
|
|
deploy_piece(nodes, attrs, 0)
|
|
end
|
|
|
|
private
|
|
def select_ifaces(var,name)
|
|
result = false
|
|
if var.is_a?(Array)
|
|
result = true if var.include?(name)
|
|
elsif var.is_a?(String)
|
|
result = true if var == name
|
|
end
|
|
end
|
|
|
|
def nodes_status(nodes, status, data_to_merge)
|
|
{'nodes' => nodes.map { |n| {'uid' => n['uid'], 'status' => status}.merge(data_to_merge) }}
|
|
end
|
|
|
|
def validate_nodes(nodes)
|
|
if nodes.empty?
|
|
Astute.logger.info "#{@ctx.task_id}: Nodes to deploy are not provided. Do nothing."
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
def calculate_networks(data, hwinterfaces)
|
|
interfaces = {}
|
|
data ||= []
|
|
Astute.logger.info "calculate_networks function was provided with #{data.size} interfaces"
|
|
data.each do |net|
|
|
Astute.logger.debug "Calculating network for #{net.inspect}"
|
|
if net['vlan'] && net['vlan'] != 0
|
|
name = [net['dev'], net['vlan']].join('.')
|
|
else
|
|
name = net['dev']
|
|
end
|
|
unless interfaces.has_key?(name)
|
|
interfaces[name] = {'interface' => name, 'ipaddr' => []}
|
|
end
|
|
iface = interfaces[name]
|
|
if net['name'] == 'admin'
|
|
if iface['ipaddr'].size > 0
|
|
Astute.logger.error "Admin network interferes with openstack nets"
|
|
end
|
|
iface['ipaddr'] += ['dhcp']
|
|
else
|
|
if iface['ipaddr'].any?{|x| x == 'dhcp'}
|
|
Astute.logger.error "Admin network interferes with openstack nets"
|
|
end
|
|
if net['ip']
|
|
iface['ipaddr'] += [net['ip']]
|
|
end
|
|
if net['gateway'] && net['name'] =~ /^public$/i
|
|
iface['gateway'] = net['gateway']
|
|
end
|
|
end
|
|
Astute.logger.debug "Calculated network for interface: #{name}, data: #{iface.inspect}"
|
|
end
|
|
interfaces['lo'] = {'interface'=>'lo', 'ipaddr'=>['127.0.0.1/8']} unless interfaces.has_key?('lo')
|
|
hwinterfaces.each do |i|
|
|
unless interfaces.has_key?(i['name'])
|
|
interfaces[i['name']] = {'interface' => i['name'], 'ipaddr' => []}
|
|
end
|
|
end
|
|
interfaces.keys.each do |i|
|
|
interfaces[i]['ipaddr'] = 'none' if interfaces[i]['ipaddr'].size == 0
|
|
interfaces[i]['ipaddr'] = 'dhcp' if interfaces[i]['ipaddr'] == ['dhcp']
|
|
end
|
|
interfaces
|
|
end
|
|
end
|
|
end
|