├── boxes └── dummy │ ├── metadata.json │ ├── dummy.box │ └── README.md ├── Rakefile ├── lib ├── opennebula-provider │ ├── version.rb │ ├── plugin.rb │ ├── action │ │ ├── create.rb │ │ ├── stop.rb │ │ ├── start.rb │ │ ├── resume.rb │ │ ├── suspend.rb │ │ ├── wait_for_ssh.rb │ │ ├── destroy.rb │ │ ├── wait_for_state.rb │ │ ├── read_ssh_info.rb │ │ ├── check_state.rb │ │ └── messages.rb │ ├── errors.rb │ ├── provider.rb │ ├── driver.rb │ ├── config.rb │ ├── helpers │ │ └── fog.rb │ └── action.rb └── opennebula-provider.rb ├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── opennebula-provider.gemspec └── locales └── en.yml /boxes/dummy/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "opennebula" 3 | } 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | Bundler::GemHelper.install_tasks 4 | -------------------------------------------------------------------------------- /boxes/dummy/dummy.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eucher/opennebula-provider/HEAD/boxes/dummy/dummy.box -------------------------------------------------------------------------------- /lib/opennebula-provider/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | VERSION = '1.1.6' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /boxes/dummy/README.md: -------------------------------------------------------------------------------- 1 | # Dummy box 2 | 3 | Vagrant providers each require a custom provider-specific box format. This folder 4 | contains a "dummy" box that allows you to use the plugin without the need to create 5 | a custom base box. 6 | 7 | To turn this into a "box", run: 8 | 9 | ``` 10 | tar cvzf dummy.box ./metadata.json 11 | ``` 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development do 4 | gem 'fog' 5 | gem 'opennebula' 6 | gem 'vagrant', :git => 'https://github.com/mitchellh/vagrant.git' 7 | gem 'vagrant-spec', :git => 'https://github.com/mitchellh/vagrant-spec.git' 8 | end 9 | 10 | group :plugins do 11 | gem 'opennebula-provider', path: '.' 12 | end 13 | -------------------------------------------------------------------------------- /lib/opennebula-provider.rb: -------------------------------------------------------------------------------- 1 | require 'opennebula-provider/errors' 2 | require 'opennebula-provider/plugin' 3 | 4 | module VagrantPlugins 5 | module OpenNebulaProvider 6 | def self.source_root 7 | @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) 8 | end 9 | 10 | I18n.load_path << File.expand_path('locales/en.yml', source_root) 11 | I18n.reload! 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/opennebula-provider/plugin.rb: -------------------------------------------------------------------------------- 1 | require_relative 'version' 2 | require_relative 'driver' 3 | 4 | require 'vagrant' 5 | 6 | module VagrantPlugins 7 | module OpenNebulaProvider 8 | class Plugin < Vagrant.plugin('2') 9 | name 'opennebula-provider' 10 | 11 | config(:opennebula, :provider) do 12 | require_relative 'config' 13 | Config 14 | end 15 | 16 | provider(:opennebula, parallel: true) do 17 | require_relative 'provider' 18 | Provider 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/create.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class Create 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::create') 8 | end 9 | 10 | def call(env) 11 | @logger.info I18n.t('opennebula_provider.info.creating') 12 | driver = env[:machine].provider.driver 13 | env[:machine].id = driver.create 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/stop.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class Stop 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::stop') 8 | end 9 | 10 | def call(env) 11 | env[:ui].info I18n.t('opennebula_provider.info.halt', machine: env[:machine].id) 12 | driver = env[:machine].provider.driver 13 | driver.stop(env[:machine].id) 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/start.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class Start 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::start') 8 | end 9 | 10 | def call(env) 11 | env[:ui].info I18n.t('opennebula_provider.info.start', machine: env[:machine].id) 12 | driver = env[:machine].provider.driver 13 | driver.start(env[:machine].id) 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/resume.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class Resume 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::resume') 8 | end 9 | 10 | def call(env) 11 | env[:ui].info I18n.t('opennebula_provider.info.resume', machine: env[:machine].id) 12 | driver = env[:machine].provider.driver 13 | driver.resume(env[:machine].id) 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/suspend.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class Suspend 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::suspend') 8 | end 9 | 10 | def call(env) 11 | env[:ui].info I18n.t('opennebula_provider.info.suspend', machine: env[:machine].id) 12 | driver = env[:machine].provider.driver 13 | driver.suspend(env[:machine].id) 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/wait_for_ssh.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class WaitForSSH 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::wait_for_ssh') 8 | end 9 | 10 | def call(env) 11 | env[:ui].info I18n.t('opennebula_provider.info.waiting_for_sshd') 12 | unless env[:interrupted] 13 | env[:machine].communicate.ready? 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/destroy.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class Destroy 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::destroy') 8 | end 9 | 10 | def call(env) 11 | @logger.info I18n.t('opennebula_provider.info.destroying', machine: env[:machine].id) 12 | driver = env[:machine].provider.driver 13 | driver.delete(env[:machine].id) 14 | env[:machine].id = nil 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/wait_for_state.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class WaitForState 5 | def initialize(app, env, state) 6 | @app = app 7 | @state = state 8 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::wait_for_state') 9 | end 10 | 11 | def call(env) 12 | driver = env[:machine].provider.driver 13 | driver.wait_for_state(env, @state) do |last_state| 14 | # env[:machine_state] = last_state.to_sym 15 | break if last_state == :error 16 | end 17 | @app.call(env) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/read_ssh_info.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class ReadSSHInfo 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::read_ssh_info') 8 | end 9 | 10 | def call(env) 11 | env[:machine_ssh_info] = read_ssh_info(env[:machine]) 12 | @app.call(env) 13 | end 14 | 15 | def read_ssh_info(machine) 16 | return nil if machine.id.nil? 17 | driver = machine.provider.driver 18 | 19 | host = driver.ssh_info(machine.id) 20 | { host: host, port: 22 } unless host.nil? 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/check_state.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class CheckState 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::check_state') 8 | end 9 | 10 | def call(env) 11 | env[:machine_state] = check_state(env[:machine]) 12 | @logger.info I18n.t('opennebula_provider.info.state', state: env[:machine_state]) 13 | @app.call(env) 14 | end 15 | 16 | def check_state(machine) 17 | return :not_created unless machine.id 18 | driver = machine.provider.driver 19 | driver.state(machine.id) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/opennebula-provider/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Errors 4 | class OpenNebulaProviderError < Vagrant::Errors::VagrantError 5 | error_namespace('opennebula_provider.errors') 6 | end 7 | 8 | class AllocateError < OpenNebulaProviderError 9 | error_key(:allocate) 10 | end 11 | 12 | class AuthError < OpenNebulaProviderError 13 | error_key(:auth) 14 | end 15 | 16 | class ComputeError < OpenNebulaProviderError 17 | error_key(:compute) 18 | end 19 | 20 | class ConnectError < OpenNebulaProviderError 21 | error_key(:connect) 22 | end 23 | 24 | class ResourceError < OpenNebulaProviderError 25 | error_key(:resource) 26 | end 27 | 28 | class QuotaError < OpenNebulaProviderError 29 | error_key(:quota) 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Cherdancev Evgeni 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/opennebula-provider/provider.rb: -------------------------------------------------------------------------------- 1 | require_relative 'action' 2 | 3 | module VagrantPlugins 4 | module OpenNebulaProvider 5 | class Provider < Vagrant.plugin('2', :provider) 6 | def initialize(machine) 7 | @logger = Log4r::Logger.new('vagrant::provider::opennebula') 8 | @machine = machine 9 | end 10 | 11 | def action(name) 12 | return Action.send(name) if Action.respond_to?(name) 13 | nil 14 | end 15 | 16 | def driver 17 | return @driver if @driver 18 | @driver = Driver.new 19 | @driver.config = @machine.config 20 | @driver.provider_config = @machine.provider_config 21 | @driver.connect 22 | 23 | @driver 24 | end 25 | 26 | def ssh_info 27 | env = @machine.action('read_ssh_info') 28 | env[:machine_ssh_info] 29 | end 30 | 31 | def state 32 | state = driver.state(@machine.id) 33 | 34 | short = I18n.t("opennebula_provider.states.short_#{state}") 35 | long = I18n.t("opennebula_provider.states.long_#{state}") 36 | 37 | Vagrant::MachineState.new(state, short, long) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/opennebula-provider/driver.rb: -------------------------------------------------------------------------------- 1 | require 'opennebula-provider/helpers/fog' 2 | 3 | module VagrantPlugins 4 | module OpenNebulaProvider 5 | class Driver 6 | include Vagrant::Util::Retryable 7 | 8 | def initialize 9 | @fog_driver ||= VagrantPlugins::OpenNebulaProvider::Helpers::FogApi.new 10 | end 11 | 12 | def config=(config) 13 | @fog_driver.config = config 14 | end 15 | 16 | def provider_config=(provider_config) 17 | @fog_driver.provider_config = provider_config 18 | end 19 | 20 | def connect 21 | @fog_driver.connect 22 | end 23 | 24 | def state(cid) 25 | @fog_driver.machine_state(cid) 26 | end 27 | 28 | def create 29 | @fog_driver.compute 30 | end 31 | 32 | def delete(cid) 33 | @fog_driver.delete(cid) 34 | end 35 | 36 | def start(cid) 37 | @fog_driver.start(cid) 38 | end 39 | 40 | def stop(cid) 41 | @fog_driver.stop(cid) 42 | end 43 | 44 | def suspend(cid) 45 | @fog_driver.suspend(cid) 46 | end 47 | 48 | def resume(cid) 49 | @fog_driver.resume(cid) 50 | end 51 | 52 | def ssh_info(cid) 53 | @fog_driver.ssh_info(cid) 54 | end 55 | 56 | def wait_for_state(env, state) 57 | retryable(tries: 60, sleep: 2) do 58 | next if env[:interrupted] 59 | env[:machine_state] = @fog_driver.machine_state(env[:machine].id) 60 | 61 | yield env[:machine_state] if block_given? 62 | 63 | if env[:machine_state] != state 64 | fail Errors::ComputeError, 65 | error: "Can not wait when instance will be in '#{state}' status, " \ 66 | "last status is '#{env[:machine_state]}'" 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenNebula::Provider 2 | 3 | [![Gem Version](https://badge.fury.io/rb/opennebula-provider.svg)](https://rubygems.org/gems/opennebula-provider) 4 | 5 | This is a Vagrant 1.5+ plugin that add an OpenNebula provider to Vagrant. 6 | 7 | ## Features 8 | 9 | * Boot OpenNebula instances 10 | * SSH into instances 11 | * Provision the instances with any built-in Vagrant provisioner 12 | * Minimal synced folder support via `rsync` 13 | 14 | ## Installation 15 | 16 | ``` 17 | $ vagrant plugin install opennebula-provider 18 | ... 19 | $ vagrant up --provider=opennebula 20 | ... 21 | ``` 22 | 23 | ## Usage 24 | 25 | ``` 26 | Vagrant.configure("2") do |config| 27 | config.vm.box = "dummy" 28 | 29 | config.vm.provider :opennebula do |one, override| 30 | one.endpoint = 'http://opennebula.server:2633/RPC2' 31 | one.username = 'YOUR NAME' 32 | one.password = 'YOUR PASSWORD' 33 | one.template_id = 123 34 | one.title = 'my-vm' 35 | end 36 | end 37 | ``` 38 | 39 | ## Configuration 40 | 41 | * `endpoint` - OpenNebula RPC endpoint (like 'http://127.0.0.1:2633/RPC2') 42 | * `username` - OpenNebula username 43 | * `password` - OpenNebula password 44 | * `template_id` - OpenNebula template id 45 | * `template_name` - OpenNebula template name 46 | * `title` - OpenNebula instance name 47 | * `memory` - An instance memory in MB 48 | * `cpu` - An instance cpus 49 | * `vcpu` - An instance virtual cpus 50 | * `disk_size` - Disk size, in MB, for templates with ONLY one disk 51 | * `user_variables` - An instance specified variables (like 'CPU_MODEL = [ MODEL = host-passthrough ]') 52 | 53 | You can use `template_name` parameters instead `template_id` to define template by name and if there are multiple templates with the same name will be used the most recent. 54 | 55 | You can use ONE_USER, ONE_PASSWORD, ONE_XMLRPC (or ONE_ENDPOINT) environment variables 56 | instead of defining it in Vagrantfile. 57 | However, Vagrantfile's provider config has more priority. 58 | 59 | ## Contributing 60 | 61 | 1. Fork it 62 | 2. Create your feature branch (`git checkout -b my-new-feature`) 63 | 3. Commit your changes (`git commit -am 'Add some feature'`) 64 | 4. Push to the branch (`git push origin my-new-feature`) 65 | 5. Create new Pull Request 66 | -------------------------------------------------------------------------------- /opennebula-provider.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | $:.unshift File.expand_path('../lib', __FILE__) 3 | require 'opennebula-provider/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'opennebula-provider' 7 | spec.version = VagrantPlugins::OpenNebulaProvider::VERSION 8 | spec.authors = ['Cherdancev Evgeni'] 9 | spec.email = ['cyber@sibnet.ru'] 10 | spec.description = %q(OpenNebula provider for Vagrant) 11 | spec.summary = spec.description 12 | spec.homepage = '' 13 | spec.license = 'MIT' 14 | 15 | root_path = File.dirname(__FILE__) 16 | all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") } 17 | all_files.reject! { |file| [".", ".."].include?(File.basename(file)) } 18 | gitignore_path = File.join(root_path, ".gitignore") 19 | gitignore = File.readlines(gitignore_path) 20 | gitignore.map! { |line| line.chomp.strip } 21 | gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ } 22 | 23 | unignored_files = all_files.reject do |file| 24 | # Ignore any directories, the gemspec only cares about files 25 | next true if File.directory?(file) 26 | 27 | # Ignore any paths that match anything in the gitignore. We do 28 | # two tests here: 29 | # 30 | # - First, test to see if the entire path matches the gitignore. 31 | # - Second, match if the basename does, this makes it so that things 32 | # like '.DS_Store' will match sub-directories too (same behavior 33 | # as git). 34 | # 35 | gitignore.any? do |ignore| 36 | File.fnmatch(ignore, file, File::FNM_PATHNAME) || 37 | File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME) 38 | end 39 | end 40 | 41 | spec.files = unignored_files 42 | 43 | spec.executables = unignored_files.map { |f| f[/^bin\/(.*)/, 1] }.compact 44 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 45 | spec.require_paths = ["lib"] 46 | 47 | spec.add_runtime_dependency 'opennebula', '~> 4.14' 48 | spec.add_runtime_dependency 'fog', '~> 1.38', '>= 1.38.0' 49 | 50 | spec.add_development_dependency 'bundler', '~> 1.3' 51 | spec.add_development_dependency 'rake', '~> 0' 52 | spec.add_development_dependency 'rspec', '~> 0' 53 | end 54 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action/messages.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | module Action 4 | class MessageAlreadyCreated 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info I18n.t('opennebula_provider.info.already_created') 11 | @app.call(env) 12 | end 13 | end 14 | 15 | class MessageAlreadyDestroyed 16 | def initialize(app, env) 17 | @app = app 18 | end 19 | 20 | def call(env) 21 | env[:ui].info I18n.t('opennebula_provider.info.already_destroyed') 22 | @app.call(env) 23 | end 24 | end 25 | 26 | class MessageAlreadyHalted 27 | def initialize(app, env) 28 | @app = app 29 | end 30 | 31 | def call(env) 32 | env[:ui].info I18n.t('opennebula_provider.info.already_halted') 33 | @app.call(env) 34 | end 35 | end 36 | 37 | class MessageAlreadySuspended 38 | def initialize(app, env) 39 | @app = app 40 | end 41 | 42 | def call(env) 43 | env[:ui].info I18n.t('opennebula_provider.info.already_suspended') 44 | @app.call(env) 45 | end 46 | end 47 | 48 | class MessageNotCreated 49 | def initialize(app, env) 50 | @app = app 51 | end 52 | 53 | def call(env) 54 | env[:ui].info I18n.t('opennebula_provider.info.not_created') 55 | @app.call(env) 56 | end 57 | end 58 | 59 | class MessageWillNotDestroy 60 | def initialize(app, env) 61 | @app = app 62 | end 63 | 64 | def call(env) 65 | env[:ui].info I18n.t('opennebula_provider.info.will_not_destroy') 66 | @app.call(env) 67 | end 68 | end 69 | 70 | class MessageSuspended 71 | def initialize(app, env) 72 | @app = app 73 | end 74 | 75 | def call(env) 76 | env[:ui].info I18n.t('opennebula_provider.info.suspended') 77 | @app.call(env) 78 | end 79 | end 80 | 81 | class MessageHalted 82 | def initialize(app, env) 83 | @app = app 84 | end 85 | 86 | def call(env) 87 | env[:ui].info I18n.t('opennebula_provider.info.halted') 88 | @app.call(env) 89 | end 90 | end 91 | 92 | class MessageInErrorState 93 | def initialize(app, env) 94 | @app = app 95 | end 96 | 97 | def call(env) 98 | env[:ui].info I18n.t('opennebula_provider.info.error') 99 | @app.call(env) 100 | end 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/opennebula-provider/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module OpenNebulaProvider 3 | class Config < Vagrant.plugin('2', :config) 4 | attr_accessor :endpoint 5 | attr_accessor :auth 6 | attr_accessor :username 7 | attr_accessor :password 8 | attr_accessor :template 9 | attr_accessor :template_id 10 | attr_accessor :template_name 11 | attr_accessor :os_tpl 12 | attr_accessor :resource_tpl 13 | attr_accessor :title 14 | attr_accessor :memory 15 | attr_accessor :cpu 16 | attr_accessor :vcpu 17 | attr_accessor :disk_size 18 | attr_accessor :user_variables 19 | 20 | def initialize 21 | @endpoint = ENV['ONE_XMLRPC'] || ENV['ONE_ENDPOINT'] || UNSET_VALUE 22 | @auth = UNSET_VALUE 23 | @username = ENV['ONE_USER'] || UNSET_VALUE 24 | @password = ENV['ONE_PASSWORD'] || UNSET_VALUE 25 | @template_id = UNSET_VALUE 26 | @template_name = UNSET_VALUE 27 | @os_tpl = UNSET_VALUE 28 | @resource_tpl = UNSET_VALUE 29 | @title = UNSET_VALUE 30 | @memory = UNSET_VALUE 31 | @cpu = UNSET_VALUE 32 | @vcpu = UNSET_VALUE 33 | @disk_size = UNSET_VALUE 34 | @user_variables = UNSET_VALUE 35 | end 36 | 37 | def finalize! 38 | @endpoint = nil if @endpoint == UNSET_VALUE || @endpoint.empty? 39 | @auth = 'basic' if @auth == UNSET_VALUE 40 | @username = nil if @username == UNSET_VALUE 41 | @password = nil if @password == UNSET_VALUE 42 | @template_id = nil if @template_id == UNSET_VALUE 43 | @template_name = nil if @template_name == UNSET_VALUE 44 | if @template_id 45 | @template = @template_id 46 | elsif @template_name 47 | @template = @template_name 48 | elsif @template == UNSET_VALUE 49 | @template = nil 50 | end 51 | @resource_tpl = 'small' if @resource_tpl == UNSET_VALUE 52 | @title = nil if @title == UNSET_VALUE 53 | @memory = nil if @memory == UNSET_VALUE 54 | @vcpu = nil if @vcpu == UNSET_VALUE 55 | @cpu = nil if @cpu == UNSET_VALUE 56 | if @cpu && ! @vcpu 57 | @vcpu = @cpu 58 | end 59 | @disk_size = nil if @disk_size == UNSET_VALUE 60 | @user_variables = nil if @user_variables == UNSET_VALUE 61 | end 62 | 63 | def validate(machine) 64 | errors = [] 65 | errors << I18n.t('opennebula_provider.config.endpoint') unless @endpoint 66 | errors << I18n.t('opennebula_provider.config.username') unless @username 67 | errors << I18n.t('opennebula_provider.config.password') unless @password 68 | errors << I18n.t('opennebula_provider.config.template') unless @template 69 | errors << I18n.t('opennebula_provider.config.title') unless @title 70 | 71 | { 'OpenNebula provider' => errors } 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | opennebula_provider: 3 | info: 4 | connecting: |- 5 | Connecting to OpenNebula 6 | creating: |- 7 | Creating a new instance... 8 | not_created: |- 9 | Instance is not created 10 | already_created: |- 11 | Instance is already created 12 | already_destroyed: |- 13 | Instance is already destroyed 14 | already_halted: |- 15 | Instance is already halted 16 | already_suspended: |- 17 | Instance is already suspended 18 | destroying: |- 19 | Instance %{machine} is destroying... 20 | will_not_destroy: |- 21 | Instance will not be destroyed, declined 22 | state: |- 23 | Instance state is %{state} 24 | halt: |- 25 | Instance is shutdown... 26 | halted: |- 27 | Instance is halted 28 | start: |- 29 | Starting the instance... 30 | suspend: |- 31 | Instance is suspending... 32 | suspended: |- 33 | Instance is suspended 34 | resume: |- 35 | Resuming instance... 36 | waiting_for_sshd: |- 37 | Instance is active, waiting for sshd daemon startup... 38 | error: |- 39 | The instance is in error state 40 | 41 | states: 42 | short_not_created: |- 43 | not created 44 | long_not_created: |- 45 | The OpenNebula instance is not created 46 | 47 | short_inactive: |- 48 | inactive 49 | long_inactive: |- 50 | Instance is inactive 51 | 52 | short_active: |- 53 | active 54 | long_active: |- 55 | Instance is active 56 | 57 | short_waiting: |- 58 | waiting 59 | long_waiting: |- 60 | Instance is waiting to create or boot 61 | 62 | short_boot: |- 63 | boot 64 | long_waiting: |- 65 | OpenNebula is waiting for the hypervisor to create the VM 66 | 67 | short_running: |- 68 | running 69 | long_running: |- 70 | The OpenNebula instance is running 71 | 72 | short_error: |- 73 | error 74 | long_error: |- 75 | The OpenNebula instance is can not start 76 | 77 | short_stopped: |- 78 | stopped 79 | long_stopped: |- 80 | The OpenNebula instance is in stopped state (halted) 81 | 82 | short_suspended: |- 83 | suspended 84 | long_suspended: |- 85 | The OpenNebula instance is suspended 86 | 87 | short_LCM_INIT_7: |- 88 | FAILED 89 | long_LCM_INIT_7: |- 90 | The OpenNebula instance is in FAILED state 91 | 92 | config: 93 | endpoint: |- 94 | rOCCI-server endpoint is not set 95 | username: |- 96 | Username is not set 97 | password: |- 98 | Password is not set 99 | template: |- 100 | An template must defined via "template_id" or "template_name" 101 | title: |- 102 | VM title is not set 103 | 104 | compute: 105 | template_missing: |- 106 | Template "%{template}" is not defined in OpenNebula 107 | resource_size_missing: |- 108 | Resource size "%{template}" is not defined in rOCCI-server 109 | 110 | errors: 111 | allocate: |- 112 | Allocation error! %{error} 113 | auth: |- 114 | Authentication error! %{error} 115 | compute: |- 116 | Compute error! %{error} 117 | connect: |- 118 | Connect error! %{error} 119 | resource: |- 120 | Resource error! %{error} 121 | quota: |- 122 | Quota error! %{error} 123 | -------------------------------------------------------------------------------- /lib/opennebula-provider/helpers/fog.rb: -------------------------------------------------------------------------------- 1 | require 'fog' 2 | 3 | module VagrantPlugins 4 | module OpenNebulaProvider 5 | module Helpers 6 | class FogApi 7 | attr_accessor :config 8 | 9 | def initialize 10 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::helpers::fog') 11 | end 12 | 13 | def provider_config=(provider_config) 14 | @provider_config = provider_config 15 | @options = { 16 | provider: 'OpenNebula', 17 | opennebula_endpoint: provider_config.endpoint, 18 | opennebula_username: provider_config.username, 19 | opennebula_password: provider_config.password 20 | } 21 | end 22 | 23 | def connect 24 | @logger = Log4r::Logger.new('vagrant::provider::opennebula::helpers::fog') 25 | @logger.info 'Connect to OpenNebula RPC' 26 | @fog_client = Fog::Compute.new(@options) 27 | rc = @fog_client.client.get_version 28 | if rc.is_a?(OpenNebula::Error) 29 | case rc.errno 30 | when 256 31 | raise Errors::AuthError, error: rc.message 32 | when 4369 33 | raise Errors::ConnectError, error: rc.message 34 | end 35 | end 36 | end 37 | 38 | def compute 39 | @logger.info 'compute!' 40 | newvm = @fog_client.servers.new 41 | if @provider_config.template_id 42 | newvm.flavor = @fog_client.flavors.get @provider_config.template_id 43 | elsif @provider_config.template_name 44 | flavors = @fog_client.flavors.get_by_filter({name: @provider_config.template_name}) 45 | if flavors.nil? 46 | fail Errors::ComputeError, error: I18n.t('opennebula_provider.compute.template_missing', template: @provider_config.template_name) 47 | end 48 | newvm.flavor = flavors.last 49 | end 50 | if newvm.flavor.nil? 51 | fail Errors::ComputeError, error: I18n.t('opennebula_provider.compute.template_missing', template: @provider_config.template) 52 | end 53 | 54 | newvm.name = @provider_config.title if @provider_config.title 55 | if @config.vm.hostname != nil 56 | newvm.flavor.context = (newvm.flavor.context).merge({ 'SET_HOSTNAME' => @config.vm.hostname }) 57 | end 58 | newvm.flavor.memory = @provider_config.memory unless @provider_config.memory.nil? 59 | newvm.flavor.cpu = @provider_config.cpu unless @provider_config.cpu.nil? 60 | newvm.flavor.vcpu = @provider_config.vcpu unless @provider_config.vcpu.nil? 61 | 62 | if newvm.flavor.disk.instance_of? Array 63 | fail Errors::AllocateError, error: I18n.t( 64 | 'opennebula_provider.errors.allocate', 65 | error: 'Template has more than one disk attached and `disk_size` ' + 66 | 'feature is supported only for templates with only one') 67 | end 68 | 69 | if @provider_config.disk_size != nil 70 | newvm.flavor.disk["SIZE"] = @provider_config.disk_size 71 | end 72 | newvm.flavor.user_variables = @provider_config.user_variables unless @provider_config.user_variables.nil? 73 | @logger.warn "Deploying VM with options #{newvm.flavor.inspect}" 74 | vm = newvm.save 75 | vm.id 76 | rescue RuntimeError => e 77 | raise Errors::ComputeError, error: e 78 | end 79 | 80 | def stop(id) 81 | @logger.info 'stop!' 82 | @fog_client.servers.get(id).stop 83 | end 84 | 85 | def start(id) 86 | @logger.info 'start!' 87 | @fog_client.servers.get(id).start 88 | end 89 | 90 | def delete(id) 91 | @logger.info 'delete!' 92 | @fog_client.servers.get(id).destroy 93 | end 94 | 95 | def suspend(id) 96 | @logger.info 'suspend!' 97 | @fog_client.servers.get(id).suspend 98 | end 99 | 100 | def resume(id) 101 | @logger.info 'resume!' 102 | @fog_client.servers.get(id).resume 103 | end 104 | 105 | def machine_state(id) 106 | if id 107 | begin 108 | desc = @fog_client.servers.get(id) #state LCM_INIT & RUNNING status 7 && UNDEPLOYED 109 | rescue ArgumentError => e 110 | raise Errors::ResourceError, error: e 111 | end 112 | return :not_created if desc.nil? 113 | 114 | return get_pretty_status(desc.state, desc.status) 115 | else 116 | return :not_created 117 | end 118 | end 119 | 120 | def ssh_info(id) 121 | desc = @fog_client.servers.get(id) 122 | desc.ip unless desc.nil? 123 | end 124 | 125 | private 126 | def get_pretty_status(state, status) 127 | pretty = "#{state}_#{status}" 128 | case state 129 | when 'LCM_INIT' 130 | case status 131 | when 1, 3 132 | pretty = 'pending' 133 | when 4 134 | pretty = 'stopped' 135 | when 5 136 | pretty = 'suspended' 137 | when 7 138 | pretty = 'error' 139 | end 140 | when 'PROLOG', 'PROLOG_RESUME' 141 | case status 142 | when 3 143 | pretty = 'prolog' 144 | end 145 | when 'BOOT' 146 | case status 147 | when 3 148 | pretty = 'boot' 149 | end 150 | when 'RUNNING' 151 | case status 152 | when 3 153 | pretty = 'active' 154 | end 155 | when 'BOOT_STOPPED' 156 | case status 157 | when 3 158 | pretty = 'boot' 159 | end 160 | when 'BOOT_STOPPED_FAILURE' 161 | case status 162 | when 3 163 | pretty = 'error' 164 | end 165 | end 166 | pretty.to_sym 167 | end 168 | end 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /lib/opennebula-provider/action.rb: -------------------------------------------------------------------------------- 1 | require_relative 'action/check_state' 2 | require_relative 'action/create' 3 | require_relative 'action/destroy' 4 | require_relative 'action/messages' 5 | require_relative 'action/read_ssh_info' 6 | require_relative 'action/resume' 7 | require_relative 'action/start' 8 | require_relative 'action/stop' 9 | require_relative 'action/suspend' 10 | require_relative 'action/wait_for_state' 11 | require_relative 'action/wait_for_ssh' 12 | 13 | module VagrantPlugins 14 | module OpenNebulaProvider 15 | module Action 16 | include Vagrant::Action::Builtin 17 | 18 | def self.destroy 19 | Vagrant::Action::Builder.new.tap do |b| 20 | b.use ConfigValidate 21 | b.use Call, CheckState do |env, b1| 22 | case env[:machine_state] 23 | when :active, :error, :suspended, :inactive, :stopped 24 | b1.use Call, DestroyConfirm do |env1, b2| 25 | if env1[:result] 26 | b2.use Destroy 27 | b2.use ProvisionerCleanup if defined?(ProvisionerCleanup) 28 | else 29 | b2.use MessageWillNotDestroy 30 | end 31 | end 32 | when :not_created 33 | b1.use MessageNotCreated 34 | next 35 | end 36 | end 37 | end 38 | end 39 | 40 | def self.up 41 | Vagrant::Action::Builder.new.tap do |b| 42 | b.use ConfigValidate 43 | b.use Call, CheckState do |env, b1| 44 | case env[:machine_state] 45 | when :active 46 | b1.use MessageAlreadyCreated 47 | next 48 | when :suspended 49 | # TODO: uncomment this with patching fog 50 | # b1.use Resume 51 | when :stopped 52 | b1.use Start 53 | when :not_created, :inactive 54 | b1.use Create 55 | when :error # in state FAILED 56 | b1.use MessageInErrorState 57 | next 58 | end 59 | b1.use WaitForCommunicator, [:pending, :prolog, :boot, :active] 60 | b1.use SyncedFolders 61 | end 62 | end 63 | end 64 | 65 | def self.halt 66 | Vagrant::Action::Builder.new.tap do |b| 67 | b.use ConfigValidate 68 | b.use Call, CheckState do |env1, b1| 69 | case env1[:machine_state] 70 | when :active 71 | b1.use Stop 72 | b1.use WaitForState, :stopped 73 | when :suspended 74 | b1.use MessageSuspended 75 | when :stopped 76 | b1.use MessageAlreadyHalted 77 | when :not_created, :inactive 78 | b1.use MessageNotCreated 79 | end 80 | end 81 | end 82 | end 83 | 84 | def self.suspend 85 | Vagrant::Action::Builder.new.tap do |b| 86 | b.use ConfigValidate 87 | b.use Call, CheckState do |env1, b1| 88 | case env1[:machine_state] 89 | when :active 90 | # TODO: uncomment this with patching fog 91 | # b1.use Suspend 92 | # b1.use WaitForState, :suspended 93 | when :suspended 94 | b1.use MessageAlreadySuspended 95 | when :not_created, :inactive 96 | b1.use MessageNotCreated 97 | end 98 | end 99 | end 100 | end 101 | 102 | def self.resume 103 | Vagrant::Action::Builder.new.tap do |b| 104 | b.use ConfigValidate 105 | b.use Call, CheckState do |env1, b1| 106 | case env1[:machine_state] 107 | when :suspended 108 | # TODO: uncomment this with patching fog 109 | # b1.use Resume 110 | # b1.use WaitForState, :active 111 | when :stopped 112 | b1.use MessageHalted 113 | when :not_created, :inactive 114 | b1.use MessageNotCreated 115 | end 116 | end 117 | end 118 | end 119 | 120 | def self.reload 121 | Vagrant::Action::Builder.new.tap do |b| 122 | b.use ConfigValidate 123 | b.use Call, CheckState do |env1, b1| 124 | case env1[:machine_state] 125 | when :not_created 126 | b1.use MessageNotCreated 127 | next 128 | end 129 | 130 | b1.use halt 131 | b1.use up 132 | end 133 | end 134 | end 135 | 136 | def self.check_state 137 | Vagrant::Action::Builder.new.tap do |b| 138 | b.use ConfigValidate 139 | b.use CheckState 140 | end 141 | end 142 | 143 | def self.provision 144 | Vagrant::Action::Builder.new.tap do |b| 145 | b.use ConfigValidate 146 | b.use Call, CheckState do |env1, b1| 147 | case env1[:machine_state] 148 | when :not_created, :inactive 149 | b1.use MessageNotCreated 150 | when :suspended 151 | b1.use MessageSuspended 152 | when :stopped 153 | b1.use MessageHalted 154 | else 155 | b1.use Provision 156 | end 157 | end 158 | end 159 | end 160 | 161 | def self.read_ssh_info 162 | Vagrant::Action::Builder.new.tap do |b| 163 | b.use ReadSSHInfo 164 | end 165 | end 166 | 167 | def self.ssh_run 168 | Vagrant::Action::Builder.new.tap do |b| 169 | b.use ConfigValidate 170 | b.use Call, CheckState do |env1, b1| 171 | case env1[:machine_state] 172 | when :not_created, :inactive 173 | b1.use MessageNotCreated 174 | when :suspended 175 | b1.use MessageSuspended 176 | when :stopped 177 | b1.use MessageStopped 178 | else 179 | b1.use SSHRun 180 | end 181 | end 182 | end 183 | end 184 | 185 | def self.ssh 186 | Vagrant::Action::Builder.new.tap do |b| 187 | b.use ConfigValidate 188 | b.use Call, CheckState do |env1, b1| 189 | case env1[:machine_state] 190 | when :not_created, :inactive 191 | b1.use MessageNotCreated 192 | when :suspended 193 | b1.use MessageSuspended 194 | when :stopped 195 | b1.use MessageStopped 196 | else 197 | b1.use SSHExec 198 | end 199 | end 200 | end 201 | end 202 | end 203 | end 204 | end 205 | --------------------------------------------------------------------------------