├── .gitignore ├── Rakefile ├── Gemfile ├── lib ├── knife-xenserver │ └── version.rb └── chef │ └── knife │ ├── xenserver_host_list.rb │ ├── xenserver_network_list.rb │ ├── xenserver_vm_poweron.rb │ ├── xenserver_vm_poweroff.rb │ ├── xenserver_template_list.rb │ ├── xenserver_vm_delete.rb │ ├── xenserver_sr_list.rb │ ├── xenserver_sr_create.rb │ ├── xenserver_base.rb │ ├── xenserver_vm_list.rb │ ├── xenserver_template_create.rb │ └── xenserver_vm_create.rb ├── vendor └── fog │ └── lib │ └── fog │ └── xenserver │ └── requests │ └── compute │ └── add_attribute.rb ├── knife-xenserver.gemspec ├── CHANGELOG.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /lib/knife-xenserver/version.rb: -------------------------------------------------------------------------------- 1 | module Knife 2 | module XenServer 3 | VERSION = "1.4.4" 4 | MAJOR, MINOR, TINY = VERSION.split('.') 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /vendor/fog/lib/fog/xenserver/requests/compute/add_attribute.rb: -------------------------------------------------------------------------------- 1 | module Fog 2 | module Compute 3 | class XenServer 4 | 5 | class Real 6 | 7 | require 'fog/xenserver/parser' 8 | 9 | def add_attribute( klass, ref, attr_name, *value ) 10 | @connection.request({:parser => Fog::Parsers::XenServer::Base.new, :method => "#{klass}.add_#{attr_name.gsub('-','_')}"}, ref, *value) 11 | end 12 | 13 | end 14 | 15 | class Mock 16 | 17 | def add_attribute( klass, ref, attr_name, value ) 18 | Fog::Mock.not_implemented 19 | end 20 | 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /knife-xenserver.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "knife-xenserver/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "knife-xenserver" 7 | s.version = Knife::XenServer::VERSION 8 | s.has_rdoc = true 9 | s.authors = ["Sergio Rubio", "Pedro Perez"] 10 | s.email = ["info@bvox.net"] 11 | s.homepage = "http://github.com/bvox/knife-xenserver" 12 | s.summary = "XenServer Support for Chef's Knife Command" 13 | s.description = s.summary 14 | s.extra_rdoc_files = ["README.md", "LICENSE" ] 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | 20 | s.add_dependency "terminal-table", "~> 1.4" 21 | s.add_dependency "chef", "~> 11.18" 22 | s.add_dependency "fog", "~> 1.34" 23 | s.add_dependency "uuidtools", "~> 2.1.5" 24 | 25 | s.add_development_dependency "bundler", "~> 1.10" 26 | 27 | s.require_paths = ["lib"] 28 | end 29 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_host_list.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require 'chef/knife/xenserver_base' 19 | 20 | class Chef 21 | class Knife 22 | class XenserverHostList < Knife 23 | 24 | include Knife::XenserverBase 25 | 26 | banner "knife xenserver host list (options)" 27 | 28 | def run 29 | hosts = connection.hosts 30 | table = table do |t| 31 | t.headings = %w{NAME UUID} 32 | hosts.each do |host| 33 | t << [host.name, host.uuid] 34 | end 35 | end 36 | puts table if !hosts.empty? 37 | end 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_network_list.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 BVox S.L. 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverNetworkList < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver network list" 28 | 29 | def run 30 | networks = connection.networks 31 | table = table do |t| 32 | t.headings = %w{NETWORK_NAME VIFs PIFs BRIDGE} 33 | networks.each do |net| 34 | pifs = net.pifs.map { |p| p.device } 35 | t << [net.name, net.__vifs.size, pifs.join(","), net.bridge] 36 | end 37 | end 38 | puts table if !networks.empty? 39 | end 40 | 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_vm_poweron.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 Sergio Rubio 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverVmPoweron < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver vm poweron VM_NAME [VM_NAME] (options)" 28 | 29 | def run 30 | powered_on = [] 31 | connection.servers.each do |vm| 32 | @name_args.each do |vm_name| 33 | if (vm_name == vm.name) or (vm_name == vm.uuid) 34 | vm.start 35 | powered_on << vm_name 36 | ui.info("#{'Powered on'.yellow} virtual machine #{vm.name.yellow} [uuid: #{vm.uuid}]") 37 | end 38 | end 39 | end 40 | @name_args.each do |vm_name| 41 | ui.warn "Virtual Machine #{vm_name} not found" if not powered_on.include?(vm_name) 42 | end 43 | end 44 | 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_vm_poweroff.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 Sergio Rubio 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverVmPoweroff < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver vm poweroff VM_NAME [VM_NAME] (options)" 28 | 29 | option :hard, 30 | :long => "--hard", 31 | :description => "Force VM shutdown", 32 | :boolean => true, 33 | :default => false, 34 | :proc => Proc.new { true } 35 | 36 | def run 37 | powered_off = [] 38 | connection.servers.each do |vm| 39 | @name_args.each do |vm_name| 40 | if (vm_name == vm.name) or (vm_name == vm.uuid) 41 | confirm("Do you really want to #{'poweroff'.bold.red} this virtual machine #{vm.name.bold.red}") 42 | if config[:hard] 43 | vm.stop 'hard' 44 | else 45 | vm.stop 'clean' 46 | end 47 | powered_off << vm_name 48 | ui.info("#{'Powered off'.yellow} virtual machine #{vm.name.yellow} [uuid: #{vm.uuid}]") 49 | end 50 | end 51 | end 52 | @name_args.each do |vm_name| 53 | ui.warn "Virtual Machine #{vm_name} not found" if not powered_off.include?(vm_name) 54 | end 55 | end 56 | 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.3.2 - 18 jun 2013 2 | 3 | * Fix --sr-type option parsing for 'sr list' command. Thanks to @elvin159. 4 | 5 | # 1.3.1 - Thy 06 Jun 2013 6 | 7 | * Fixing some --extra-vdis parsing issues 8 | * Fix nasty bug while trying to roll back destroying the created VM 9 | 10 | # 1.3.0 - Thy 06 Jun 2013 11 | 12 | * New **sr list** command to list storage repositories. 13 | * Experimental **sr create** command to create a Storage Repository. 14 | * The **vm create** command now supports --extra-vdis option to 15 | create additional VDIs and attach them to the VM. 16 | * New **host list** command: currently lists UUID only. 17 | 18 | 19 | # 1.2.3 - Fri 07 Dec 2012 20 | 21 | * Drop alchemist gem, fixing some compat issues with some ruby 1.9 22 | implementations. See #6. 23 | 24 | # 1.2.2 - Thu 06 Dec 2012 25 | 26 | * Fixed --storage-repository parameter in 'template create' command 27 | 28 | # 1.2.1 - Mon 12 Nov 2012 29 | 30 | * Maks3w patches for xenserver-automated related stuff 31 | 32 | * Added --match flag to the 'vm delete' command. 33 | 34 | If --match is used, every VM matching VM_NAME will be deleted 35 | 36 | * Deprecate --force-delete option in 'vm delete' since --yes should 37 | be used to confirm (force). 38 | 39 | # 1.2 - Thu 25 Oct 2012 40 | 41 | * Greatly improved 'vm list' command 42 | 43 | - Table columns can now be disabled in output: 44 | 45 | knife xenserver vm list --no-uuid \ 46 | --no-power \ 47 | --no-networks \ 48 | --no-tools 49 | 50 | - Print CSV output with --csv instead of a regular ASCII table 51 | 52 | knife xenserver vm list --csv 53 | 54 | * Added --match option to 'vm list' 55 | 56 | Print only VMs whose name matches the given regex: 57 | 58 | knife xenserver vm list --match '^my-vm.*?devel.bvox.net$' 59 | 60 | * FIX: Set exit status to 1 when XenServer auth fails 61 | * FIX: Print error if XenServer host is not defined 62 | 63 | # 1.1 - 2012/10/21 64 | 65 | * Fixed --no-host-key-verify vm create flag 66 | * added power on/off commands 67 | * Allow setting of network information on create for Ubuntu systems 68 | (@krobertson, @adamlau) 69 | 70 | See https://github.com/bvox/knife-xenserver/pull/1 71 | 72 | # 0.1.0 - 2012/04/04 73 | 74 | * Initial release 75 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_template_list.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 BVox S.L. 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverTemplateList < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver template list" 28 | 29 | option :include_builtin, 30 | :long => "--include-builtin", 31 | :description => "Include built-in templates", 32 | :boolean => true, 33 | :proc => Proc.new { true } 34 | 35 | def run 36 | $stdout.sync = true 37 | templates = connection.servers.custom_templates || [] 38 | table = table do |t| 39 | t.headings = %w{NAME MEMORY GUEST_TOOLS NETWORKS} 40 | if templates.empty? and !config[:include_builtin] 41 | ui.warn "No custom templates found. Use --include-builtin to list them all." 42 | end 43 | if config[:include_builtin] 44 | templates += connection.servers.builtin_templates 45 | end 46 | templates.each do |vm| 47 | networks = [] 48 | vm.vifs.each do |vif| 49 | name = vif.network.name 50 | if name.size > 20 51 | name = name[0..16] + '...' 52 | end 53 | networks << name 54 | end 55 | networks = networks.join("\n") 56 | mem = bytes_to_megabytes(vm.memory_static_max) 57 | t << ["#{vm.name}\n #{ui.color('uuid: ', :yellow)}#{vm.uuid}", mem, vm.tools_installed?, networks] 58 | end 59 | end 60 | puts table if !templates.empty? 61 | end 62 | 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_vm_delete.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2011 Sergio Rubio 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverVmDelete < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver vm delete VM_NAME [VM_NAME] (options)" 28 | 29 | option :force_delete, 30 | :long => "--force-delete NO", 31 | :default => 'no', 32 | :description => "Do not confirm VM deletion when yes" 33 | 34 | option :match, 35 | :long => "--match", 36 | :description => "Delete VMs matching VM_NAME (regex)", 37 | :boolean => true 38 | 39 | def run 40 | if config[:force_delete] =~ /y|yes/i 41 | ui.warn "--force-delete is deprecated." 42 | ui.warn "Use --yes to confirm deletion." 43 | config[:yes] = true 44 | end 45 | deleted = [] 46 | connection.servers.each do |vm| 47 | to_delete = [] 48 | @name_args.each do |vm_name| 49 | if config[:match] 50 | if vm.name =~ /#{vm_name}/ 51 | to_delete << vm 52 | end 53 | else 54 | if (vm_name == vm.name) or (vm_name == vm.uuid) 55 | to_delete << vm 56 | end 57 | end 58 | end 59 | to_delete.each do |vm| 60 | confirm("Do you really want to #{'delete'.bold.red} this virtual machine #{vm.name.bold.red}") 61 | vm.destroy 62 | deleted << vm.name 63 | ui.info("#{'Deleted'.yellow} virtual machine #{vm.name.yellow} [uuid: #{vm.uuid}]") 64 | end 65 | end 66 | @name_args.each do |vm_name| 67 | ui.warn "Virtual Machine#{'(s) matching' if config[:match]} '#{vm_name}' not found" if deleted.size == 0 68 | end 69 | end 70 | 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_sr_list.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require 'chef/knife/xenserver_base' 19 | 20 | class Chef 21 | class Knife 22 | class XenserverSrList < Knife 23 | 24 | include Knife::XenserverBase 25 | 26 | banner "knife xenserver sr list (options)" 27 | 28 | option :sr_type, 29 | :long => "--sr-types type1[,type2...]", 30 | :description => "List only the types specified (comma separated types)", 31 | :default => 'ext,lvm' 32 | 33 | option :numeric_utilisation, 34 | :long => "--[no-]numeric-utilisation", 35 | :description => "Print SR utilisation in bytes", 36 | :default => false, 37 | :boolean => true 38 | 39 | def run 40 | # Finding the current host (the one we used to get the connection) 41 | #xshost = config[:xenserver_host] || Chef::Config[:knife][:xenserver_host] 42 | #address = Resolv.getaddress xshost 43 | #host = connection.hosts.find { |h| h.address == address } 44 | 45 | # Storage Repositories belong to the pool, 46 | # There's no way to list host storage repositories AFAIK 47 | repositories = connection.storage_repositories 48 | table = table do |t| 49 | t.headings = %w{NAME TYPE UTILISATION UUID} 50 | valid_types = config[:sr_type].split(',').map { |t| t.strip } 51 | repositories.each do |sr| 52 | # we only list LVM and EXT repositories by default 53 | next unless valid_types.include? sr.type 54 | if config[:numeric_utilisation] 55 | utilisation = sr.physical_utilisation 56 | else 57 | if sr.physical_size.to_i > 0 58 | utilisation = (sr.physical_utilisation.to_i * 100)/sr.physical_size.to_f 59 | utilisation = "%.2f%" % utilisation 60 | else 61 | utilisation = "100%" 62 | end 63 | end 64 | t << [sr.name, sr.type, utilisation, sr.uuid] 65 | end 66 | end 67 | puts table if !repositories.empty? 68 | end 69 | 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_sr_create.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require 'chef/knife/xenserver_base' 19 | require 'resolv' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverSrCreate < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver sr create (options)" 28 | 29 | option :sr_name, 30 | :long => "--sr-name NAME", 31 | :description => "The Storage Repository name" 32 | 33 | option :sr_type, 34 | :long => "--sr-type TYPE", 35 | :description => "The Storage Repository type (ext|lvm)" 36 | 37 | option :sr_device, 38 | :long => "--sr-device PATH", 39 | :description => "Block device path in host" 40 | 41 | option :sr_host, 42 | :long => "--sr-host UUID", 43 | :description => "Host where the Storage Repository will be created" 44 | 45 | def run 46 | sr_name = config[:sr_name] 47 | if not sr_name 48 | ui.error("Invalid Storage Repository name (--sr-name)") 49 | exit 1 50 | end 51 | 52 | sr_type = config[:sr_type] 53 | if not sr_type 54 | ui.error("Invalid Storage Repository type (--sr-type)") 55 | exit 1 56 | end 57 | 58 | sr_device = config[:sr_device] 59 | if not sr_device 60 | ui.error("Invalid Storage Repository device (--sr-device)") 61 | exit 1 62 | end 63 | 64 | hosts = connection.hosts 65 | sr_host = config[:sr_host] 66 | if sr_host.nil? and hosts.size > 1 67 | if not sr_host 68 | ui.error("More than one host found in the pool.") 69 | ui.error("--sr-host parameter is mandatory") 70 | exit 1 71 | end 72 | end 73 | 74 | if sr_host 75 | # Find the host in the pool 76 | host = connection.hosts.find { |h| h.uuid == sr_host } 77 | else 78 | # We only have one hosts 79 | host = hosts.first 80 | end 81 | 82 | # The given host was not found in the pool 83 | if host.nil? 84 | ui.error "Host #{sr_host} not found in the pool" 85 | exit 1 86 | end 87 | 88 | dconfig = { :device => sr_device } 89 | puts "Creating SR #{sr_name.yellow} in host #{host.name} (#{sr_type}, #{sr_device})..." 90 | vm = connection.storage_repositories.create :name => sr_name, 91 | :host => host, 92 | :device_config => dconfig, 93 | :type => sr_type 94 | 95 | end 96 | 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: BVox S.L. (c) 2012 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife' 20 | $: << File.dirname(__FILE__) + "/../../../vendor/fog/lib/" 21 | require 'fog' 22 | require 'colored' 23 | 24 | class Chef 25 | class Knife 26 | module XenserverBase 27 | 28 | def self.included(includer) 29 | includer.class_eval do 30 | 31 | deps do 32 | require 'net/ssh/multi' 33 | require 'readline' 34 | require 'chef/json_compat' 35 | require 'terminal-table/import' 36 | end 37 | 38 | option :xenserver_password, 39 | :long => "--xenserver-password PASSWORD", 40 | :description => "Your XenServer password" 41 | 42 | option :xenserver_username, 43 | :long => "--xenserver-username USERNAME", 44 | :default => "root", 45 | :description => "Your XenServer username (default 'root')" 46 | 47 | option :xenserver_host, 48 | :long => "--xenserver-host ADDRESS", 49 | :description => "Your XenServer host address" 50 | end 51 | end 52 | 53 | def connection 54 | if not @connection 55 | host = config[:xenserver_host] || Chef::Config[:knife][:xenserver_host] 56 | if host.nil? 57 | ui.error "XenServer host not defined." 58 | ui.error "Use --xenserver-host or add it to the knife config file." 59 | exit 1 60 | end 61 | username = config[:xenserver_username] || Chef::Config[:knife][:xenserver_username] 62 | password = config[:xenserver_password] || Chef::Config[:knife][:xenserver_password] 63 | ui.info "Connecting to XenServer host #{host.yellow}..." 64 | begin 65 | @connection = Fog::Compute.new({ 66 | :provider => 'XenServer', 67 | :xenserver_url => host, 68 | :xenserver_username => username, 69 | :xenserver_password => password, 70 | }) 71 | rescue SocketError => e 72 | ui.error "Error connecting to the hypervisor: #{host}" 73 | exit 1 74 | rescue Fog::XenServer::InvalidLogin => e 75 | ui.error "Error connecting to the hypervisor: #{host}" 76 | ui.error "Check the username and password." 77 | exit 1 78 | rescue => e 79 | ui.error "Error connecting to the hypervisor" 80 | ui.error "#{e.class} #{e.message}" 81 | exit 1 82 | end 83 | 84 | else 85 | @connection 86 | end 87 | end 88 | 89 | def locate_config_value(key) 90 | key = key.to_sym 91 | Chef::Config[:knife][key] || config[key] 92 | end 93 | 94 | def bytes_to_megabytes(bytes) 95 | (bytes.to_i / (1024.0 * 1024.0)).round 96 | end 97 | 98 | end 99 | end 100 | end 101 | 102 | 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![BVox](http://bvox.net/images/logo-bvox-big.png) 2 | # knife-xenserver 3 | 4 | Provision virtual machines with Citrix XenServer and Opscode Chef. 5 | 6 | ## Upgrading knife-xenserver 7 | 8 | When upgrading knife-xenserver, it's very important to remove older knife-xenserver versions 9 | 10 | gem update knife-xenserver 11 | gem clean knife-xenserver 12 | 13 | ## Usage 14 | 15 | knife xenserver --help 16 | 17 | ## Examples 18 | 19 | List all the VMs 20 | 21 | knife xenserver vm list --xenserver-host fooserver \ 22 | --xenserver-username root \ 23 | --xenserver-password secret 24 | 25 | 26 | List custom templates 27 | 28 | knife xenserver template list --xenserver-host fooserver \ 29 | --xenserver-username root \ 30 | --xenserver-password secret 31 | 32 | Include built-in templates too 33 | 34 | knife xenserver template list --xenserver-host fooserver \ 35 | --xenserver-username root \ 36 | --xenserver-password secret \ 37 | --include-builtin 38 | 39 | Create a new template from a VHD file (PV by default, use --hvm otherwise) 40 | 41 | knife xenserver template create --vm-name ubuntu-precise-amd64 \ 42 | --vm-disk ubuntu-precise.vhd \ 43 | --vm-memory 512 \ 44 | --vm-networks 'Integration-VLAN' \ 45 | --storage-repository 'Local storage' \ 46 | --xenserver-password changeme \ 47 | --xenserver-host 10.0.0.2 48 | 49 | 50 | Create a VM from template ed089e35-fb49-f555-4e20-9b7f3db8df2d and bootstrap it using the 'root' user and password 'secret'. The VM is created without VIFs, inherited VIFs from template are removed by default (use --keep-template-networks to avoid that) 51 | 52 | knife xenserver vm create --vm-template ed089e35-fb49-f555-4e20-9b7f3db8df2d \ 53 | --vm-name foobar --ssh-user root \ 54 | --ssh-password secret 55 | 56 | Create a VM from template and add two custom VIFs in networks 'Integration-VLAN' and 'Another-VLAN', with MAC address 11:22:33:44:55:66 for the first VIF 57 | 58 | knife xenserver vm create --vm-template ed089e35-fb49-f555-4e20-9b7f3db8df2d \ 59 | --vm-name foobar --ssh-user root \ 60 | --ssh-password secret \ 61 | --vm-networks 'Integration-VLAN,Another-VLAN' \ 62 | --mac-addresses 11:22:33:44:55:66 63 | 64 | Create a VM from template and supply ip/host/domain configuration. Requires installation of xe-automater scripts 65 | (https://github.com/krobertson/xenserver-automater) 66 | 67 | knife xenserver vm create --vm-template my-template -x root --keep-template-networks \ 68 | --vm-name my-hostname \ 69 | --vm-ip 172.20.1.25 --vm-netmask 255.255.0.0 --vm-gateway 172.20.0.1 --vm-dns 172.20.0.3 \ 70 | --vm-domain my-domain.local 71 | 72 | The domU/guest will also need xe-guest-utilities installed. You can then list xenstore attributes running 'xenstore-ls vm-data' inside domU. 73 | 74 | List hypervisor networks 75 | 76 | knife xenserver network list 77 | 78 | ## Sample .chef/knife.rb config 79 | 80 | knife[:xenserver_password] = "secret" 81 | knife[:xenserver_username] = "root" 82 | knife[:xenserver_host] = "xenserver-real" 83 | 84 | 85 | # Building the rubygem 86 | 87 | gem build knife-xenserver.gemspec 88 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_vm_list.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 BVox S.L. 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | 21 | class Chef 22 | class Knife 23 | class XenserverVmList < Knife 24 | 25 | include Knife::XenserverBase 26 | 27 | banner "knife xenserver vm list (options)" 28 | 29 | option :csv, 30 | :long => "--csv", 31 | :description => "Comma separated list of VMs", 32 | :boolean => true, 33 | :default => false 34 | 35 | option :uuid, 36 | :long => "--[no-]uuid", 37 | :description => "Print/Hide VM UUID", 38 | :boolean => true, 39 | :default => true 40 | 41 | option :tools, 42 | :long => "--[no-]tools", 43 | :description => "Print/Hide XenTools Availability", 44 | :boolean => true, 45 | :default => true 46 | 47 | option :networks, 48 | :long => "--[no-]networks", 49 | :description => "Print/Hide VM Networks", 50 | :boolean => true, 51 | :default => true 52 | 53 | option :mem, 54 | :long => "--[no-]mem", 55 | :description => "Print/Hide VM Memory", 56 | :boolean => true, 57 | :default => true 58 | 59 | option :ips, 60 | :long => "--[no-]ips", 61 | :description => "Print/Hide VM IPs", 62 | :boolean => true, 63 | :default => true 64 | 65 | option :power_state, 66 | :long => "--[no-]power-state", 67 | :description => "Print/Hide VM Power State", 68 | :boolean => true, 69 | :default => true 70 | 71 | option :match, 72 | :long => "--match REGEX", 73 | :description => "Print only VMs whose name matches REGEX" 74 | 75 | def gen_headings 76 | headings = %w{NAME} 77 | if config[:mem] 78 | headings << 'MEM' 79 | end 80 | if config[:power_state] 81 | headings << 'POWER' 82 | end 83 | if config[:tools] 84 | headings << 'TOOLS' 85 | end 86 | if config[:networks] 87 | headings << 'NETWORKS' 88 | end 89 | if config[:ips] 90 | headings << 'IPs' 91 | end 92 | headings 93 | end 94 | 95 | def gen_table 96 | # row format 97 | # [uuid, name, [ips], [networks], mem, power, tools] 98 | table = [] 99 | connection.servers.each do |vm| 100 | if config[:match] and vm.name !~ /#{config[:match]}/ 101 | next 102 | end 103 | row = [vm.uuid, vm.name] 104 | if vm.tools_installed? 105 | ips = [] 106 | vm.guest_metrics.networks.each do |k,v| 107 | ips << v 108 | end 109 | row << ips 110 | else 111 | row << [] 112 | end 113 | networks = [] 114 | vm.vifs.each do |vif| 115 | name = vif.network.name 116 | if name.size > 20 117 | name = name[0..16] + '...' 118 | end 119 | networks << name 120 | end 121 | row << networks 122 | row << bytes_to_megabytes(vm.memory_static_max) 123 | row << vm.power_state 124 | row << vm.tools_installed? 125 | table << row 126 | end 127 | table 128 | end 129 | 130 | def print_table 131 | vm_table = table do |t| 132 | t.headings = gen_headings 133 | gen_table.each do |row| 134 | # [uuid, name, [ips], [networks], mem, power, tools] 135 | uuid, name, ips, networks, mem, power, tools = row 136 | elements = [] 137 | if config[:uuid] 138 | elements << "#{uuid}\n #{ui.color('name: ', :yellow)}#{name.ljust(32)}" 139 | else 140 | elements << "#{ui.color('name: ', :yellow)}#{name.ljust(32)}" 141 | end 142 | elements << mem if config[:mem] 143 | elements << power if config[:power_state] 144 | elements << tools if config[:tools] 145 | elements << networks.join("\n") if config[:networks] 146 | elements << ips.join("\n") if config[:ips] 147 | t << elements 148 | end 149 | end 150 | puts vm_table if connection.servers.size > 0 151 | end 152 | 153 | def print_csv 154 | lines = [] 155 | header = "" 156 | gen_table.each do |row| 157 | uuid, name, ips, networks, mem, power, tools = row 158 | elements = [] 159 | elements << name 160 | elements << mem if config[:mem] 161 | elements << power if config[:power_state] 162 | elements << tools if config[:tools] 163 | elements << networks.join(";") if config[:networks] 164 | elements << ips.join(";") if config[:ips] 165 | if config[:uuid] 166 | header = "UUID,#{gen_headings.join(',')}" 167 | lines << "#{uuid},#{elements.join(',')}" 168 | else 169 | header = "#{gen_headings.join(',')}" 170 | lines << "#{elements.join(',')}" 171 | end 172 | end 173 | 174 | puts header 175 | lines.each do |l| 176 | puts l 177 | end 178 | end 179 | 180 | def run 181 | $stdout.sync = true 182 | if config[:csv] 183 | print_csv 184 | else 185 | print_table 186 | end 187 | end 188 | 189 | end 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_template_create.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 BVox S.L. 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | require 'net/scp' 21 | require 'uuidtools' 22 | 23 | class Chef 24 | class Knife 25 | class XenserverTemplateCreate < Knife 26 | 27 | include Knife::XenserverBase 28 | 29 | banner "knife xenserver template create" 30 | 31 | option :hvm, 32 | :long => "--hvm", 33 | :description => "Use HVM (default is PV)", 34 | :boolean => false, 35 | :proc => Proc.new { true } 36 | 37 | option :vm_name, 38 | :long => "--vm-name NAME", 39 | :description => "The template name" 40 | 41 | option :vm_tags, 42 | :long => "--vm-tags tag1[,tag2..]", 43 | :description => "Comma separated list of tags" 44 | 45 | option :vm_disk, 46 | :long => "--vm-disk DISK", 47 | :description => "The disk file to use" 48 | 49 | option :vm_template, 50 | :long => "--vm-template NAME", 51 | :description => "The Virtual Machine to clone (TODO)" 52 | 53 | option :vm_memory, 54 | :long => "--vm-memory AMOUNT", 55 | :description => "The memory limits of the Virtual Machine", 56 | :default => '512' 57 | 58 | option :storage_repository, 59 | :long => "--storage-repository SR", 60 | :description => "The storage repository to use", 61 | :default => 'Local storage' 62 | 63 | option :vm_networks, 64 | :short => "-N network[,network..]", 65 | :long => "--vm-networks", 66 | :description => "Network where nic is attached to" 67 | 68 | option :mac_addresses, 69 | :short => "-M mac[,mac..]", 70 | :long => "--mac-addresses", 71 | :description => "Mac address list", 72 | :default => nil 73 | 74 | def run 75 | source = config[:vm_disk] 76 | vm_name = config[:vm_name] 77 | host = config[:xenserver_host] || Chef::Config[:knife][:xenserver_host] 78 | user = config[:xenserver_username] || Chef::Config[:knife][:xenserver_username] 79 | password = config[:xenserver_password] || Chef::Config[:knife][:xenserver_password] 80 | if host.nil? 81 | ui.error "Invalid Xen host. Use #{'--xenserver-host'.red.bold} argument." 82 | exit 1 83 | end 84 | if user.nil? 85 | ui.error "Invalid Xen username. Use #{'--xenserver-username'.red.bold} argument." 86 | exit 1 87 | end 88 | if password.nil? 89 | ui.error "Invalid Xen password. Use #{'--xenserver-password'.red.bold} argument." 90 | exit 1 91 | end 92 | 93 | if vm_name.nil? 94 | ui.error "Invalid name for the template. Use #{'--vm-name'.red.bold} argument." 95 | exit 1 96 | end 97 | 98 | if source.nil? or not File.exist?(source) 99 | ui.error "Invalid source disk #{(source || '').bold}." 100 | ui.error "#{'--vm-disk'.red.bold} argument is mandatory" \ 101 | if source.nil? 102 | exit 1 103 | end 104 | if source !~ /\.vhd$/ 105 | ui.error "Invalid source disk #{source.red.bold}. I need a VHD file." 106 | exit 1 107 | end 108 | 109 | # Create the VM but do not start/provision it 110 | if config[:hvm] 111 | ui.info "HVM".yellow + " template selected" 112 | pv_bootloader = 'eliloader' 113 | hvm_boot_policy = 'BIOS order' 114 | pv_args = '' 115 | else 116 | ui.info "PV".yellow + " template selected" 117 | pv_bootloader = 'pygrub' 118 | hvm_boot_policy = '' 119 | pv_args = '-- console=hvc0' 120 | end 121 | 122 | ui.info "Creating VM #{vm_name.yellow} on #{host.yellow}..." 123 | 124 | # We will create the VDI in this SR 125 | sr = connection.storage_repositories.find { |sr| sr.name == config[:storage_repository] } 126 | # Upload and replace the VDI with our template 127 | uuid = UUIDTools::UUID.random_create.to_s 128 | dest = "/var/run/sr-mount/#{sr.uuid}/#{uuid}.vhd" 129 | Net::SSH.start(host, user, :password => password) do |ssh| 130 | puts "Uploading file #{File.basename(source).yellow}..." 131 | puts "Destination SR #{sr.name.yellow}" 132 | ssh.scp.upload!(source, dest) do |ch, name, sent, total| 133 | p = (sent.to_f * 100 / total.to_f).to_i.to_s 134 | print "\rProgress: #{p.yellow.bold}% completed" 135 | end 136 | end 137 | 138 | sr.scan 139 | # Create a ~8GB VDI in storage repository 'Local Storage' 140 | #vdi = connection.vdis.create :name => "#{vm_name}-disk1", 141 | # :storage_repository => sr, 142 | # :description => "#{vm_name}-disk1", 143 | # :virtual_size => '8589934592' # ~8GB in bytes 144 | 145 | vdi = nil 146 | sr.vdis.each do |v| 147 | if v.uuid == uuid 148 | v.set_attribute 'name_label', "#{vm_name}-template" 149 | vdi = v 150 | break 151 | end 152 | end 153 | 154 | mem = (config[:vm_memory].to_i * 1024 * 1024).to_s 155 | vm = connection.servers.new :name => "#{vm_name}", 156 | :affinity => connection.hosts.first, 157 | :other_config => {}, 158 | :pv_bootloader => pv_bootloader, 159 | :hvm_boot_policy => hvm_boot_policy, 160 | :pv_args => pv_args, 161 | :memory_static_max => mem, 162 | :memory_static_min => mem, 163 | :memory_dynamic_max => mem, 164 | :memory_dynamic_min => mem 165 | vm.save 166 | if config[:vm_tags] 167 | vm.set_attribute 'tags', config[:vm_tags].split(',') 168 | end 169 | 170 | if config[:vm_networks] 171 | create_nics(config[:vm_networks], config[:mac_addresses], vm) 172 | end 173 | # Add the required VBD to the VM 174 | connection.vbds.create :server => vm, :vdi => vdi 175 | puts "\nDone." 176 | 177 | end 178 | 179 | def create_nics(networks, macs, vm) 180 | net_arr = networks.split(/,/).map { |x| { :network => x } } 181 | nics = [] 182 | if macs 183 | mac_arr = macs.split(/,/) 184 | nics = net_arr.each_index { |x| net_arr[x][:mac_address] = mac_arr[x] if mac_arr[x] and !mac_arr[x].empty? } 185 | else 186 | nics = net_arr 187 | end 188 | networks = connection.networks 189 | highest_device = 0 190 | vm.vifs.each { |vif| highest_device = vif.device.to_i if vif.device.to_i > highest_device } 191 | nic_count = 0 192 | nics.each do |n| 193 | net = networks.find { |net| net.name == n[:network] } 194 | if net.nil? 195 | ui.error "Network #{n[:network]} not found" 196 | exit 1 197 | end 198 | nic_count += 1 199 | c = { 200 | 'MAC_autogenerated' => n[:mac_address].nil? ? 'True':'False', 201 | 'VM' => vm.reference, 202 | 'network' => net.reference, 203 | 'MAC' => n[:mac_address] || '', 204 | 'device' => (highest_device + nic_count).to_s, 205 | 'MTU' => '0', 206 | 'other_config' => {}, 207 | 'qos_algorithm_type' => 'ratelimit', 208 | 'qos_algorithm_params' => {} 209 | } 210 | connection.create_vif_custom c 211 | end 212 | end 213 | 214 | end 215 | end 216 | end 217 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lib/chef/knife/xenserver_vm_create.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Sergio Rubio () 3 | # Copyright:: Copyright (c) 2012 BVox S.L. 4 | # License:: Apache License, Version 2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | require 'chef/knife/xenserver_base' 20 | require 'singleton' 21 | 22 | class Chef 23 | class Knife 24 | class XenserverVmCreate < Knife 25 | 26 | include Knife::XenserverBase 27 | 28 | deps do 29 | require 'readline' 30 | require 'chef/json_compat' 31 | require 'chef/knife/bootstrap' 32 | Chef::Knife::Bootstrap.load_deps 33 | end 34 | 35 | banner "knife xenserver vm create (options)" 36 | 37 | option :vm_template, 38 | :long => "--vm-template NAME", 39 | :description => "The Virtual Machine Template to use" 40 | 41 | option :vm_name, 42 | :long => "--vm-name NAME", 43 | :description => "The Virtual Machine name" 44 | 45 | option :vm_tags, 46 | :long => "--vm-tags tag1[,tag2..]", 47 | :description => "Comma separated list of tags" 48 | 49 | option :chef_node_name, 50 | :short => "-N NAME", 51 | :long => "--node-name NAME", 52 | :description => "The Chef node name for your new node" 53 | 54 | option :vm_memory, 55 | :long => "--vm-memory AMOUNT", 56 | :description => "The memory limits of the Virtual Machine", 57 | :default => '512' 58 | 59 | option :vm_cpus, 60 | :long => "--vm-cpus AMOUNT", 61 | :description => "The VCPUs of the Virtual Machine", 62 | :default => '1' 63 | 64 | option :bootstrap_version, 65 | :long => "--bootstrap-version VERSION", 66 | :description => "The version of Chef to install", 67 | :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v } 68 | 69 | option :distro, 70 | :short => "-d DISTRO", 71 | :long => "--distro DISTRO", 72 | :description => "Bootstrap a distro using a template; default is 'ubuntu10.04-gems'", 73 | :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d }, 74 | :default => "ubuntu10.04-gems" 75 | 76 | option :template_file, 77 | :long => "--template-file TEMPLATE", 78 | :description => "Full path to location of template to use", 79 | :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t }, 80 | :default => false 81 | 82 | option :run_list, 83 | :short => "-r RUN_LIST", 84 | :long => "--run-list RUN_LIST", 85 | :description => "Comma separated list of roles/recipes to apply", 86 | :proc => lambda { |o| o.split(/[\s,]+/) }, 87 | :default => [] 88 | 89 | option :first_boot_attributes, 90 | :short => "-j JSON_ATTRIBS", 91 | :long => "--json-attributes", 92 | :description => "A JSON string to be added to the first run of chef-client", 93 | :proc => lambda { |o| JSON.parse(o) }, 94 | :default => {} 95 | 96 | option :ssh_user, 97 | :short => "-x USERNAME", 98 | :long => "--ssh-user USERNAME", 99 | :description => "The ssh username; default is 'root'", 100 | :default => "root" 101 | 102 | option :ssh_password, 103 | :short => "-P PASSWORD", 104 | :long => "--ssh-password PASSWORD", 105 | :description => "The ssh password" 106 | 107 | option :use_sudo_password, 108 | :long => "--use-sudo-password PASSWORD", 109 | :description => "The sudo password on the vm" 110 | 111 | option :identity_file, 112 | :short => "-i IDENTITY_FILE", 113 | :long => "--identity-file IDENTITY_FILE", 114 | :description => "The SSH identity file used for authentication" 115 | 116 | option :host_key_verify, 117 | :long => "--[no-]host-key-verify", 118 | :description => "Disable host key verification", 119 | :boolean => true, 120 | :default => true 121 | 122 | option :skip_bootstrap, 123 | :long => "--skip-bootstrap", 124 | :description => "Skip bootstrap process (Deploy only mode)", 125 | :boolean => true, 126 | :default => false, 127 | :proc => Proc.new { true } 128 | 129 | option :keep_template_networks, 130 | :long => "--keep-template-networks", 131 | :description => "Do no remove template inherited networks (VIFs)", 132 | :boolean => true, 133 | :default => false, 134 | :proc => Proc.new { true } 135 | 136 | option :batch, 137 | :long => "--batch script.yml", 138 | :description => "Use a batch file to deploy multiple VMs", 139 | :default => nil 140 | 141 | option :vm_networks, 142 | :short => "-N network[,network..]", 143 | :long => "--vm-networks", 144 | :description => "Network where nic is attached to" 145 | 146 | option :mac_addresses, 147 | :short => "-M mac[,mac..]", 148 | :long => "--mac-addresses", 149 | :description => "Mac address list", 150 | :default => nil 151 | 152 | option :vm_ip, 153 | :long => '--vm-ip IP', 154 | :description => 'IP address to set in xenstore' 155 | 156 | option :vm_gateway, 157 | :long => '--vm-gateway GATEWAY', 158 | :description => 'Gateway address to set in xenstore' 159 | 160 | option :vm_netmask, 161 | :long => '--vm-netmask NETMASK', 162 | :description => 'Netmask to set in xenstore' 163 | 164 | option :vm_dns, 165 | :long => '--vm-dns NAMESERVER', 166 | :description => 'DNS servers to set in xenstore' 167 | 168 | option :vm_domain, 169 | :long => '--vm-domain DOMAIN', 170 | :description => 'DOMAIN of host to set in xenstore' 171 | 172 | option :extra_vdis, 173 | :long => '--extra-vdis "SR name":size1[,"SR NAME":size2,..]', 174 | :description => 'Create and attach additional VDIs (size in MB)' 175 | 176 | option :environment, 177 | :short => '-E ENV', 178 | :long => '--environment ENV', 179 | :description => 'Sets the chef environment to add the vm to' 180 | 181 | def tcp_test_ssh(hostname) 182 | tcp_socket = TCPSocket.new(hostname, 22) 183 | readable = IO.select([tcp_socket], nil, nil, 5) 184 | if readable 185 | Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}") 186 | yield 187 | true 188 | else 189 | false 190 | end 191 | rescue Errno::ETIMEDOUT, Errno::EPERM 192 | false 193 | rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH 194 | sleep 2 195 | false 196 | ensure 197 | tcp_socket && tcp_socket.close 198 | end 199 | 200 | def run 201 | $stdout.sync = true 202 | 203 | unless config[:vm_template] 204 | raise "You have not provided a valid template name. (--vm-template)" 205 | end 206 | 207 | vm_name = config[:vm_name] 208 | if not vm_name 209 | raise "Invalid Virtual Machine name (--vm-name)" 210 | end 211 | 212 | unless config[:vm_gateway] 213 | if config[:vm_ip] 214 | last_octet = Chef::Config[:knife][:xenserver_default_vm_gateway_last_octet] || 1 215 | config[:vm_gateway] = config[:vm_ip].gsub(/\.\d+$/, ".#{last_octet}") 216 | end 217 | end 218 | 219 | config[:vm_netmask] ||= Chef::Config[:knife][:xenserver_default_vm_netmask] 220 | config[:vm_dns] ||= Chef::Config[:knife][:xenserver_default_vm_dns] 221 | config[:vm_domain] ||= Chef::Config[:knife][:xenserver_default_vm_domain] 222 | 223 | template = connection.servers.templates.find do |s| 224 | (s.name == config[:vm_template]) or (s.uuid == config[:vm_template]) 225 | end 226 | 227 | if template.nil? 228 | raise "Template #{config[:vm_template]} not found." 229 | end 230 | 231 | puts "Creating VM #{config[:vm_name].yellow}..." 232 | puts "Using template #{template.name.yellow} [uuid: #{template.uuid}]..." 233 | 234 | vm = connection.servers.new :name => config[:vm_name], 235 | :template_name => config[:vm_template] 236 | vm.save :auto_start => false 237 | # Useful for the global exception handler 238 | @created_vm = vm 239 | 240 | if not config[:keep_template_networks] 241 | vm.vifs.each do |vif| 242 | vif.destroy 243 | end 244 | vm.reload 245 | end 246 | if config[:vm_networks] 247 | create_nics(config[:vm_networks], config[:mac_addresses], vm) 248 | end 249 | mem = (config[:vm_memory].to_i * 1024 * 1024).to_s 250 | vm.set_attribute 'memory_limits', mem, mem, mem, mem 251 | vm.set_attribute 'VCPUs_max', config[:vm_cpus] 252 | vm.set_attribute 'VCPUs_at_startup', config[:vm_cpus] 253 | 254 | # network configuration through xenstore 255 | attrs = {} 256 | (attrs['vm-data/ip'] = config[:vm_ip]) if config[:vm_ip] 257 | (attrs['vm-data/gw'] = config[:vm_gateway]) if config[:vm_gateway] 258 | (attrs['vm-data/nm'] = config[:vm_netmask]) if config[:vm_netmask] 259 | (attrs['vm-data/ns'] = config[:vm_dns]) if config[:vm_dns] 260 | (attrs['vm-data/dm'] = config[:vm_domain]) if config[:vm_domain] 261 | if !attrs.empty? 262 | puts "Adding attributes to xenstore..." 263 | vm.set_attribute 'xenstore_data', attrs 264 | end 265 | 266 | if config[:vm_tags] 267 | vm.set_attribute 'tags', config[:vm_tags].split(',') 268 | end 269 | 270 | vm.provision 271 | # Create additional VDIs (virtual disks) 272 | create_extra_vdis(vm) 273 | vm.start 274 | vm.reload 275 | 276 | puts "#{ui.color("VM Name", :cyan)}: #{vm.name}" 277 | puts "#{ui.color("VM Memory", :cyan)}: #{bytes_to_megabytes(vm.memory_static_max)} MB" 278 | 279 | if !config[:skip_bootstrap] 280 | # wait for it to be ready to do stuff 281 | print "\n#{ui.color("Waiting server... ", :magenta)}" 282 | timeout = 180 283 | found = connection.servers.all.find { |v| v.name == vm.name } 284 | servers = connection.servers 285 | if config[:vm_ip] 286 | vm.refresh 287 | print "\nTrying to #{'SSH'.yellow} to #{config[:vm_ip].yellow}... " 288 | print(".") until tcp_test_ssh(config[:vm_ip]) do 289 | sleep @initial_sleep_delay ||= 10; puts(" done") 290 | @ssh_ip = config[:vm_ip] 291 | end 292 | else 293 | loop do 294 | begin 295 | vm.refresh 296 | if not vm.guest_metrics.nil? and not vm.guest_metrics.networks.empty? 297 | networks = [] 298 | vm.guest_metrics.networks.each do |k,v| 299 | networks << v 300 | end 301 | networks = networks.join(",") 302 | puts 303 | puts "\n#{ui.color("Server IPs:", :cyan)} #{networks}" 304 | break 305 | end 306 | rescue Fog::Errors::Error 307 | print "\r#{ui.color('Waiting a valid IP', :magenta)}..." + "." * (100 - timeout) 308 | end 309 | sleep 1 310 | timeout -= 1 311 | if timeout == 0 312 | puts 313 | raise "Timeout trying to reach the VM. Couldn't find the IP address." 314 | end 315 | end 316 | print "\n#{ui.color("Waiting for sshd... ", :magenta)}" 317 | vm.guest_metrics.networks.each do |k,v| 318 | print "\nTrying to #{'SSH'.yellow} to #{v.yellow}... " 319 | print(".") until tcp_test_ssh(v) do 320 | sleep @initial_sleep_delay ||= 10; puts(" done") 321 | @ssh_ip = v 322 | end 323 | break if @ssh_ip 324 | end 325 | end 326 | 327 | bootstrap_for_node(vm).run 328 | puts "\n" 329 | puts "#{ui.color("Name", :cyan)}: #{vm.name}" 330 | puts "#{ui.color("IP Address", :cyan)}: #{@ssh_ip}" 331 | puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}" 332 | puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}" 333 | puts "#{ui.color("Done!", :green)}" 334 | else 335 | ui.warn "Skipping bootstrapping as requested." 336 | end 337 | 338 | end 339 | 340 | def bootstrap_for_node(vm) 341 | bootstrap = Chef::Knife::Bootstrap.new 342 | bootstrap.name_args = [@ssh_ip] 343 | bootstrap.config[:run_list] = config[:run_list] 344 | bootstrap.config[:first_boot_attributes] = config[:first_boot_attributes] 345 | bootstrap.config[:ssh_user] = config[:ssh_user] 346 | bootstrap.config[:identity_file] = config[:identity_file] 347 | bootstrap.config[:chef_node_name] = config[:chef_node_name] || vm.name 348 | bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version) 349 | bootstrap.config[:distro] = locate_config_value(:distro) 350 | # bootstrap will run as root...sudo (by default) also messes up Ohai on CentOS boxes 351 | bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root' 352 | bootstrap.config[:template_file] = locate_config_value(:template_file) 353 | bootstrap.config[:environment] = config[:environment] 354 | bootstrap.config[:host_key_verify] = config[:host_key_verify] 355 | bootstrap.config[:ssh_password] = config[:ssh_password] 356 | bootstrap.config[:use_sudo_password] = config[:use_sudo_password] 357 | bootstrap 358 | end 359 | 360 | def create_extra_vdis(vm) 361 | # Return if no extra VDIs were specified 362 | return unless config[:extra_vdis] 363 | count = 0 364 | 365 | vdis = config[:extra_vdis].strip.chomp.split(',') 366 | vdis.each do |vdi| 367 | count += 1 368 | # if options is "Storage Repository":size 369 | if vdi =~ /.*:.*/ 370 | sr, size = vdi.split(':') 371 | else #only size was specified 372 | sr = nil 373 | size = vdi 374 | end 375 | unless size =~ /^\d+$/ 376 | raise "Invalid VDI size. Not numeric." 377 | end 378 | # size in bytes 379 | bsize = size.to_i * 1024 * 1024 380 | 381 | # If the storage repository has been omitted, 382 | # use the default SR from the first pool, othewise 383 | # find the SR required 384 | if sr.nil? 385 | sr = connection.pools.first.default_sr 386 | ui.warn "No storage repository defined for extra VDI #{count}." 387 | ui.warn "Using default SR from Pool: #{sr.name}" 388 | else 389 | found = connection.storage_repositories.find { |s| s.name == sr } 390 | unless found 391 | raise "Storage Repository #{sr} not available" 392 | end 393 | sr = found 394 | end 395 | 396 | # Name of the VDI 397 | name = "#{config[:vm_name]}-extra-vdi-#{count}" 398 | 399 | puts "Creating extra VDI (#{size} MB, #{name}, #{sr.name || 'Default SR'})" 400 | vdi = connection.vdis.create :name => name, 401 | :storage_repository => sr, 402 | :description => name, 403 | :virtual_size => bsize.to_s 404 | 405 | begin 406 | # Attach the VBD 407 | connection.vbds.create :server => vm, 408 | :vdi => vdi, 409 | :userdevice => count.to_s, 410 | :bootable => false 411 | rescue => e 412 | ui.error "Could not attach the VBD to the server" 413 | # Try to destroy the VDI 414 | vdi.destroy rescue nil 415 | raise e 416 | end 417 | end 418 | end 419 | 420 | def create_nics(networks, macs, vm) 421 | net_arr = networks.split(/,/).map { |x| { :network => x } } 422 | nics = [] 423 | if macs 424 | mac_arr = macs.split(/,/) 425 | nics = net_arr.each_index { |x| net_arr[x][:mac_address] = mac_arr[x] if mac_arr[x] and !mac_arr[x].empty? } 426 | else 427 | nics = net_arr 428 | end 429 | networks = connection.networks 430 | highest_device = -1 431 | vm.vifs.each { |vif| highest_device = vif.device.to_i if vif.device.to_i > highest_device } 432 | nic_count = 0 433 | nics.each do |n| 434 | net = networks.find { |net| net.name == n[:network] } 435 | if net.nil? 436 | raise "Network #{n[:network]} not found" 437 | end 438 | nic_count += 1 439 | c = { 440 | 'MAC_autogenerated' => n[:mac_address].nil? ? 'True':'False', 441 | 'VM' => vm.reference, 442 | 'network' => net.reference, 443 | 'MAC' => n[:mac_address] || '', 444 | 'device' => (highest_device + nic_count).to_s, 445 | 'MTU' => '0', 446 | 'other_config' => {}, 447 | 'qos_algorithm_type' => 'ratelimit', 448 | 'qos_algorithm_params' => {} 449 | } 450 | connection.create_vif_custom c 451 | end 452 | end 453 | 454 | end 455 | end 456 | end 457 | --------------------------------------------------------------------------------