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

First implementation of Heat Stacks #170

Merged
merged 5 commits into from Nov 20, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Expand Up @@ -23,6 +23,7 @@ cloud.
* Provision the instances with any built-in Vagrant provisioner
* Boot instance from volume
* Attach Cinder volumes to the instances
* Create and delete Heat Orchestration stacks
* Minimal synced folder support via `rsync`
* Custom sub-commands within Vagrant CLI to query Openstack objects

Expand Down Expand Up @@ -204,6 +205,27 @@ end

* `volume_boot` - Volume to boot the VM from. When booting from an existing volume, `image` is not necessary and must not be provided.

### Orchestration Stacks

* `stacks` - Heat Stacks that will be automatically created when running `vagrant up`, and deleted when running `vagrant destroy`

Here comes an example that show two stacks :

```ruby
config.vm.provider :openstack do |os|
...
os.stacks = [
{
name: 'mystack1',
template: 'heat_template.yml'
}, {
name: 'mystack2',
template: '/path/to//my/heat_template.yml'
}]
end
```


### SSH authentication

* `keypair_name` - The name of the key pair register in nova to associate with the VM. The public key should
Expand Down
4 changes: 4 additions & 0 deletions source/lib/vagrant-openstack-provider/action.rb
Expand Up @@ -18,6 +18,7 @@ def self.action_destroy
b2.use Message, I18n.t('vagrant_openstack.not_created')
else
b2.use DeleteServer
b2.use DeleteStack
end
end
end
Expand Down Expand Up @@ -99,6 +100,7 @@ def self.action_up
when :not_created
b2.use Provision
b2.use SyncFolders
b2.use CreateStack
b2.use CreateServer
b2.use WaitForServerToBeAccessible
when :shutoff
Expand Down Expand Up @@ -190,6 +192,8 @@ def self.action_reload
autoload :Message, action_root.join('message')
autoload :ConnectOpenstack, action_root.join('connect_openstack')
autoload :CreateServer, action_root.join('create_server')
autoload :CreateStack, action_root.join('create_stack')
autoload :DeleteStack, action_root.join('delete_stack')
autoload :DeleteServer, action_root.join('delete_server')
autoload :StopServer, action_root.join('stop_server')
autoload :StartServer, action_root.join('start_server')
Expand Down
67 changes: 67 additions & 0 deletions source/lib/vagrant-openstack-provider/action/create_stack.rb
@@ -0,0 +1,67 @@
require 'log4r'
require 'socket'
require 'timeout'
require 'sshkey'
require 'yaml'

require 'vagrant-openstack-provider/config_resolver'
require 'vagrant-openstack-provider/utils'
require 'vagrant-openstack-provider/action/abstract_action'
require 'vagrant/util/retryable'

module VagrantPlugins
module Openstack
module Action
class CreateStack < AbstractAction
def initialize(app, _env)
@app = app
@logger = Log4r::Logger.new('vagrant_openstack::action::create_stack')
end

def execute(env)
@logger.info 'Start create stacks action'

config = env[:machine].provider_config

heat = env[:openstack_client].heat

config.stacks.each do |stack|
Copy link
Owner

Choose a reason for hiding this comment

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

undefined method `each' for nil:NilClass (NoMethodError)

When there's no stack declared in the Vagrantfile. Should be fixed by

config.stacks.each do |stack|
  ...
end unless config.stack.nil?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

env[:ui].info(I18n.t('vagrant_openstack.create_stack'))
env[:ui].info(" -- Stack Name : #{stack[:name]}")
env[:ui].info(" -- Template : #{stack[:template]}")

create_opts = {
name: stack[:name],
template: YAML.load_file(stack[:template])
Copy link
Owner

Choose a reason for hiding this comment

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

Actually, heat template can be either yaml or json. But no problem to handle json compatibility in a further pull request.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sure, will add this in heat issues

}

stack_id = heat.create_stack(env, create_opts)

file_path = "#{env[:machine].data_dir}/stack_#{stack[:name]}_id"
File.write(file_path, stack_id)

waiting_for_stack_to_be_created(env, stack[:name], stack_id)
end unless config.stacks.nil?

@app.call(env)
end

private

def waiting_for_stack_to_be_created(env, stack_name, stack_id, retry_interval = 3, timeout = 200)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I plan to refactor all the "waiting for ... to be ..." methods in a future patch

Copy link
Owner

Choose a reason for hiding this comment

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

Related to #141

@logger.info "Waiting for the stack with id #{stack_id} to be built..."
env[:ui].info(I18n.t('vagrant_openstack.waiting_for_stack'))
timeout(timeout, Errors::Timeout) do
stack_status = 'CREATE_IN_PROGRESS'
until stack_status == 'CREATE_COMPLETE'
@logger.debug('Waiting for stack to be CREATED')
stack_status = env[:openstack_client].heat.get_stack_details(env, stack_name, stack_id)['stack_status']
fail Errors::StackStatusError, stack: stack_id if stack_status == 'CREATE_FAILED'
sleep retry_interval
end
end
end
end
end
end
end
25 changes: 24 additions & 1 deletion source/lib/vagrant-openstack-provider/action/delete_server.rb
Expand Up @@ -18,11 +18,34 @@ def execute(env)
env[:ui].info(I18n.t('vagrant_openstack.deleting_server'))
env[:openstack_client].nova.delete_server(env, env[:machine].id)
env[:openstack_client].nova.delete_keypair_if_vagrant(env, env[:machine].id)
env[:machine].id = nil

waiting_for_instance_to_be_deleted(env, env[:machine].id)

end

@app.call(env)
end

private

def waiting_for_instance_to_be_deleted(env, instance_id, retry_interval = 3, timeout = 200)
@logger.info "Waiting for the instance with id #{instance_id} to be deleted..."
env[:ui].info(I18n.t('vagrant_openstack.waiting_deleted'))
timeout(timeout, Errors::Timeout) do
delete_ok = false
until delete_ok
begin
@logger.debug('Waiting for instance to be DELETED')
server_status = env[:openstack_client].nova.get_server_details(env, instance_id)['status']
fail Errors::ServerStatusError, server: instance_id if server_status == 'ERROR'
break if server_status == 'DELETED'
sleep retry_interval
rescue Errors::InstanceNotFound
delete_ok = true
end
end
end
end
end
end
end
Expand Down
72 changes: 72 additions & 0 deletions source/lib/vagrant-openstack-provider/action/delete_stack.rb
@@ -0,0 +1,72 @@
require 'log4r'
require 'socket'
require 'timeout'
require 'sshkey'
require 'yaml'

require 'vagrant-openstack-provider/config_resolver'
require 'vagrant-openstack-provider/utils'
require 'vagrant-openstack-provider/action/abstract_action'
require 'vagrant/util/retryable'

module VagrantPlugins
module Openstack
module Action
class DeleteStack < AbstractAction
def initialize(app, _env)
@app = app
@logger = Log4r::Logger.new('vagrant_openstack::action::delete_stack')
end

def execute(env)
@logger.info 'Start delete stacks action'

heat = env[:openstack_client].heat

list_stack_files(env).each do |stack|
env[:ui].info(I18n.t('vagrant_openstack.delete_stack'))
env[:ui].info(" -- Stack Name : #{stack[:name]}")
env[:ui].info(" -- Stack Id : #{stack[:id]}")

heat.delete_stack(env, stack[:name], stack[:id])

waiting_for_stack_to_be_deleted(env, stack[:name], stack[:id])
end

# This will remove all files in the .vagrant instance directory
env[:machine].id = nil

@app.call(env)
end

private

def list_stack_files(env)
stack_files = []
Dir.glob("#{env[:machine].data_dir}/stack_*_id") do |stack_file|
file_name = stack_file.split('/')[-1]
stack_files << {
name: file_name[6, (file_name.length) - 9],
id: File.read("#{stack_file}")
}
end
stack_files
end

def waiting_for_stack_to_be_deleted(env, stack_name, stack_id, retry_interval = 3, timeout = 200)
@logger.info "Waiting for the stack with id #{stack_id} to be deleted..."
env[:ui].info(I18n.t('vagrant_openstack.waiting_for_stack_deleted'))
timeout(timeout, Errors::Timeout) do
stack_status = 'DELETE_IN_PROGRESS'
until stack_status == 'DELETE_COMPLETE'
@logger.debug('Waiting for stack to be DELETED')
stack_status = env[:openstack_client].heat.get_stack_details(env, stack_name, stack_id)['stack_status']
fail Errors::StackStatusError, stack: stack_id if stack_status == 'DELETE_FAILED'
sleep retry_interval
end
end
end
end
end
end
end
50 changes: 50 additions & 0 deletions source/lib/vagrant-openstack-provider/client/heat.rb
@@ -0,0 +1,50 @@
require 'log4r'
require 'restclient'
require 'json'

require 'vagrant-openstack-provider/client/http_utils'
require 'vagrant-openstack-provider/client/domain'

module VagrantPlugins
module Openstack
class HeatClient
include Singleton
include VagrantPlugins::Openstack::HttpUtils
include VagrantPlugins::Openstack::Domain

def initialize
@logger = Log4r::Logger.new('vagrant_openstack::glance')
@session = VagrantPlugins::Openstack.session
end

def create_stack(env, options)
stack = {}.tap do |s|
s['stack_name'] = options[:name] if options[:name]
s['template'] = options[:template]
end
stack_res = post(env, "#{@session.endpoints[:orchestration]}/stacks", stack.to_json)
JSON.parse(stack_res)['stack']['id']
end

def get_stack_details(env, stack_name, stack_id)
stack_exists do
server_details = get(env, "#{@session.endpoints[:orchestration]}/stacks/#{stack_name}/#{stack_id}")
JSON.parse(server_details)['stack']
end
end

def delete_stack(env, stack_name, stack_id)
stack_exists do
delete(env, "#{@session.endpoints[:orchestration]}/stacks/#{stack_name}/#{stack_id}")
end
end

def stack_exists
return yield
rescue Errors::VagrantOpenstackError => e
raise Errors::StackNotFound if e.extra_data[:code] == 404
raise e
end
end
end
end
5 changes: 5 additions & 0 deletions source/lib/vagrant-openstack-provider/client/openstack.rb
Expand Up @@ -2,6 +2,7 @@
require 'restclient'
require 'json'

require 'vagrant-openstack-provider/client/heat'
require 'vagrant-openstack-provider/client/keystone'
require 'vagrant-openstack-provider/client/nova'
require 'vagrant-openstack-provider/client/neutron'
Expand Down Expand Up @@ -40,6 +41,10 @@ def self.nova
Openstack::NovaClient.instance
end

def self.heat
Openstack::HeatClient.instance
end

def self.neutron
Openstack::NeutronClient.instance
end
Expand Down