Class: VagrantPlugins::OpenStack::Action::CreateServer

Inherits:
Object
  • Object
show all
Includes:
Vagrant::Util::Retryable
Defined in:
lib/vagrant-openstack-plugin/action/create_server.rb

Overview

This creates the OpenStack server.

Instance Method Summary collapse

Constructor Details

#initialize(app, env) ⇒ CreateServer

Returns a new instance of CreateServer.



13
14
15
16
# File 'lib/vagrant-openstack-plugin/action/create_server.rb', line 13

def initialize(app, env)
  @app    = app
  @logger = Log4r::Logger.new("vagrant_openstack::action::create_server")
end

Instance Method Details

#call(env) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/vagrant-openstack-plugin/action/create_server.rb', line 18

def call(env)
  # Get the configs
  config   = env[:machine].provider_config

  # Find the flavor
  env[:ui].info(I18n.t("vagrant_openstack.finding_flavor"))
  flavor = find_matching(env[:openstack_compute].flavors.all, config.flavor)
  raise Errors::NoMatchingFlavor if !flavor

  # Find the image
  env[:ui].info(I18n.t("vagrant_openstack.finding_image"))
  image = find_matching(env[:openstack_compute].images, config.image)
  raise Errors::NoMatchingImage if !image

  # Figure out the name for the server
  server_name = config.server_name || env[:machine].name

  # Build the options for launching...
  options = {
    :flavor_ref  => flavor.id,
    :image_ref   => image.id,
    :name        => server_name,
    :key_name    => config.keypair_name,
    :metadata    => config.,
    :user_data   => config.user_data,
    :security_groups => config.security_groups,
    :os_scheduler_hints => config.scheduler_hints,
    :availability_zone => config.availability_zone
  }
  
  # Fallback to only one network, otherwise `config.networks` overrides
  unless config.networks
    if config.network
      config.networks = [ config.network ]
    else
      config.networks = []
    end
  end

  # Find networks if provided
  unless config.networks.empty?
    env[:ui].info(I18n.t("vagrant_openstack.finding_network"))
    options[:nics] = Array.new
    config.networks.each_with_index do |os_network_name, i|

      # Use the configured OpenStack network, if it exists.
      os_network = find_matching(env[:openstack_network].networks, os_network_name)
      if os_network
        current = { :net_id => os_network.id }

        # Match the OpenStack network to a corresponding
        # config.vm.network option.  If there is one, use that for its
        # IP address.
        config_network = env[:machine].config.vm.networks[i]
        if config_network
          ip_address = config_network[1][:ip]
          current[:v4_fixed_ip] = ip_address if ip_address
        end

        options[:nics] << current
      end
    end
    env[:ui].info("options[:nics]: #{options[:nics]}")
  end
 
  # Output the settings we're going to use to the user
  env[:ui].info(I18n.t("vagrant_openstack.launching_server"))
  env[:ui].info(" -- Flavor: #{flavor.name}")
  env[:ui].info(" -- Image: #{image.name}")
  env[:ui].info(" -- Name: #{server_name}")
  config.networks.each do |n|
    env[:ui].info(" -- Network: #{n}")
  end
  if config.security_groups
    env[:ui].info(" -- Security Groups: #{config.security_groups}")
  end

  # Create the server
  server = env[:openstack_compute].servers.create(options)

  # Store the ID right away so we can track it
  env[:machine].id = server.id

  # Wait for the server to finish building
  env[:ui].info(I18n.t("vagrant_openstack.waiting_for_build"))
  retryable(:on => Fog::Errors::TimeoutError, :tries => 200) do
    # If we're interrupted don't worry about waiting
    next if env[:interrupted]

    # Set the progress
    env[:ui].clear_line
    env[:ui].report_progress(server.progress, 100, false)

    # Wait for the server to be ready
    begin
      server.wait_for(5) { ready? }
      # Once the server is up and running assign a floating IP if we have one
      floating_ip = config.floating_ip
      # try to automatically allocate and  associate a floating IP
      if floating_ip && floating_ip.to_sym == :auto
        if config.floating_ip_pool
          env[:ui].info("Allocating floating IP address from pool: #{config.floating_ip_pool}")
          address = env[:openstack_compute].allocate_address(config.floating_ip_pool).body["floating_ip"]
          if address["ip"].nil?
            raise Errors::FloatingIPNotAllocated
          else
            floating_ip = address["ip"]
          end
        else
          addresses = env[:openstack_compute].addresses
          free_floating = addresses.find_index {|a| a.fixed_ip.nil?}
          if free_floating.nil?
            raise Errors::FloatingIPNotFound
          else
            floating_ip = addresses[free_floating].ip
          end
        end
      end
       
      # try to automatically associate the next available unassigned ip in the given pool
      if floating_ip && floating_ip.to_sym == :associate_unassigned
        if config.floating_ip_pool
          env[:ui].info("Associating floating IP address from pool: #{config.floating_ip_pool}")
          addresses = env[:openstack_compute].addresses

          # grab the next available IP in this pool which is not currently allocated:
          address = env[:openstack_compute].addresses.find { |thisone|
              (thisone.attributes[:pool].eql? config.floating_ip_pool and thisone.attributes[:instance_id].nil?)
          }

          if address.nil?
            raise Errors::FloatingUnassignedIPNotFound
          else
              floating_ip = address.attributes[:ip]
          end
          result = env[:openstack_compute].associate_address(server.id,floating_ip)
          if result[:status] != 202
            raise Errors::FloatingIPFailedAssociate
          else
            env[:ui].info("Found and Associated floating IP address #{address.attributes[:ip]} from pool #{config.floating_ip_pool}")
          end
        else
          raise Errors::FloatingUnassignedRequiresPool
        end
      end
        
      if floating_ip
        floater = env[:openstack_compute].addresses.find { |thisone| thisone.ip.eql? floating_ip }
        floater.server = server
      end
      
      # Process disks if provided
      volumes = Array.new
      if config.disks && !config.disks.empty?
        env[:ui].info(I18n.t("vagrant_openstack.creating_disks"))
        config.disks.each do |disk|
          volume = env[:openstack_compute].volumes.all.find{|v| v.name ==
                                                  disk["name"] and 
                                                v.description ==
                                                  disk["description"] and
                                                v.size ==
                                                  disk["size"] and
                                                v.ready? }
          if volume
            env[:ui].info("re-using volume: #{disk["name"]}")
            disk["volume_id"] = volume.id
          else
            env[:ui].info("creating volume: #{disk["name"]}")
            disk["volume_id"] = env[:openstack_compute].create_volume(
                                 disk["name"], disk["description"], disk["size"]).\
                                 data[:body]["volume"]["id"]
            volumes << { :id => disk["volume_id"] }
          end

          # mount points are not expected to be meaningful
          # add useful support if your cloud respects them
          begin
            # Assume instance has only one disk
            nova_offset = 1
            # Increase counter in case we have swap or ephemeral disks.
            if flavor.swap != 0
                nova_offset += 1
            end
            if flavor.ephemeral != 0
                nova_offset += 1
            end
            server.attach_volume(disk["volume_id"], "/dev/vd#{("c".."z").to_a[server.volume_attachments.length + nova_offset]}")
            server.wait_for{ volume_attachments.any?{|vol| vol["id"]==disk["volume_id"]} }
          rescue Excon::Errors::Error => e
            raise Errors::VolumeBadState, :volume => disk["name"], :state => e.message
          end
        end
      end

      # store this so we can use it later
      env[:floating_ip] = floating_ip
      
    rescue RuntimeError => e
      # If we don't have an error about a state transition, then
      # we just move on.
      raise if e.message !~ /should have transitioned/
      raise Errors::CreateBadState, :state => server.state.downcase
    end
  end

  if !env[:interrupted]
    # Clear the line one more time so the progress is removed
    env[:ui].clear_line

    # Wait for SSH to become available
    env[:ui].info(I18n.t("vagrant_openstack.waiting_for_ssh"))
    while true
      begin
        # If we're interrupted then just back out
        break if env[:interrupted]
        break if env[:machine].communicate.ready?
      rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH
      end
      sleep 2
    end

    env[:ui].info(I18n.t("vagrant_openstack.ready"))
  end

  @app.call(env)
end