Class: Vagrant::MachineIndex

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/vagrant/machine_index.rb,
lib/vagrant/machine_index/remote.rb

Overview

MachineIndex is able to manage the index of created Vagrant environments in a central location.

The MachineIndex stores a mapping of UUIDs to basic information about a machine. The UUIDs are stored with the Vagrant environment and are looked up in the machine index.

The MachineIndex stores information such as the name of a machine, the directory it was last seen at, its last known state, etc. Using this information, we can load the entire Machine object for a machine, or we can just display metadata if needed.

The internal format of the data file is currently JSON in the following structure:

{ “version”: 1, “machines”: { “uuid”: { “name”: “foo”, “provider”: “vmware_fusion”, “architecture”: “amd64”, “data_path”: “/path/to/data/dir”, “vagrantfile_path”: “/path/to/Vagrantfile”, “state”: “running”, “updated_at”: “2014-03-02 11:11:44 +0100” } } }

Defined Under Namespace

Modules: Remote Classes: Entry

Instance Method Summary collapse

Constructor Details

#initialize(data_dir) ⇒ MachineIndex

Initializes a MachineIndex at the given file location.

Parameters:

  • data_dir (Pathname)

    Path to the directory where data for the index can be stored. This folder should exist and must be writable.



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/vagrant/machine_index.rb', line 51

def initialize(data_dir)
  @data_dir   = data_dir
  @index_file = data_dir.join("index")
  @lock       = Monitor.new
  @machines  = {}
  @machine_locks = {}

  with_index_lock do
    unlocked_reload
  end
end

Instance Method Details

#delete(entry) ⇒ Boolean

Deletes a machine by UUID.

The machine being deleted with this UUID must either be locked by this index or must be unlocked.

Parameters:

  • entry (Entry)

    The entry to delete.

Returns:

  • (Boolean)

    true if delete is successful



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/vagrant/machine_index.rb', line 70

def delete(entry)
  return true if !entry.id

  @lock.synchronize do
    with_index_lock do
      return true if !@machines[entry.id]

      # If we don't have the lock, then we need to acquire it.
      if !@machine_locks[entry.id]
        raise "Unlocked delete on machine: #{entry.id}"
      end

      # Reload so we have the latest data, then delete and save
      unlocked_reload
      @machines.delete(entry.id)
      unlocked_save

      # Release access on this machine
      unlocked_release(entry.id)
    end
  end

  true
end

#each(reload = false) ⇒ Object

Iterate over every machine in the index. The yielded Entry objects will NOT be locked, so you’ll have to call #get manually to acquire the lock on them.



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/vagrant/machine_index.rb', line 98

def each(reload=false)
  if reload
    @lock.synchronize do
      with_index_lock do
        unlocked_reload
      end
    end
  end

  @machines.each do |uuid, data|
    yield Entry.new(uuid, data.merge("id" => uuid))
  end
end

#get(uuid) ⇒ MachineIndex::Entry

Accesses a machine by UUID and returns a Entry

The entry returned is locked and can’t be read again or updated by this process or any other. To unlock the machine, call #release with the entry.

You can only #set an entry (update) when the lock is held.

Parameters:

  • uuid (String)

    UUID for the machine to access.

Returns:



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
# File 'lib/vagrant/machine_index.rb', line 122

def get(uuid)
  entry = nil

  @lock.synchronize do
    with_index_lock do
      # Reload the data
      unlocked_reload

      data = find_by_prefix(uuid)
      return nil if !data
      uuid = data["id"]

      entry = Entry.new(uuid, data)

      # Lock this machine
      lock_file = lock_machine(uuid)
      if !lock_file
        raise Errors::MachineLocked,
          name: entry.name,
          provider: entry.provider
      end

      @machine_locks[uuid] = lock_file
    end
  end

  entry
end

#include?(uuid) ⇒ Boolean

Tests if the index has the given UUID.

Parameters:

  • uuid (String)

Returns:

  • (Boolean)


155
156
157
158
159
160
161
162
# File 'lib/vagrant/machine_index.rb', line 155

def include?(uuid)
  @lock.synchronize do
    with_index_lock do
      unlocked_reload
      return !!find_by_prefix(uuid)
    end
  end
end

#recover(entry) ⇒ Entry

Reinsert a machine into the global index if it has a valid existing uuid but does not currently exist in the index.

Parameters:

Returns:



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/vagrant/machine_index.rb', line 246

def recover(entry)
  @lock.synchronize do
    with_index_lock do
      # Reload the data
      unlocked_reload
      # Don't recover if entry already exists in the global
      return entry if find_by_prefix(entry.id)

      lock_file = lock_machine(entry.id)
      if !lock_file
        raise Errors::MachineLocked,
          name: entry.name,
          provider: entry.provider
      end
      @machine_locks[entry.id] = lock_file
    end
  end
  return set(entry)
end

#release(entry) ⇒ Object

Releases an entry, unlocking it.

This is an idempotent operation. It is safe to call this even if you’re unsure if an entry is locked or not.

After calling this, the previous entry should no longer be used.

Parameters:



172
173
174
175
176
# File 'lib/vagrant/machine_index.rb', line 172

def release(entry)
  @lock.synchronize do
    unlocked_release(entry.id)
  end
end

#set(entry) ⇒ Entry

Creates/updates an entry object and returns the resulting entry.

If the entry was new (no UUID), then the UUID will be set on the resulting entry and can be used. Additionally, the a lock will be created for the resulting entry, so you must #release it if you want others to be able to access it.

If the entry isn’t new (has a UUID). then this process must hold that entry’s lock or else this set will fail.

Parameters:

Returns:



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
# File 'lib/vagrant/machine_index.rb', line 190

def set(entry)
  # Get the struct and update the updated_at attribute
  struct = entry.to_json_struct

  # Set an ID if there isn't one already set
  id     = entry.id

  @lock.synchronize do
    with_index_lock do
      # Reload so we have the latest machine data. This allows other
      # processes to update their own machines without conflicting
      # with our own.
      unlocked_reload

      # If we don't have a machine ID, try to look one up
      if !id
        self.each do |other|
          if entry.name == other.name &&
            entry.provider == other.provider &&
            entry.vagrantfile_path.to_s == other.vagrantfile_path.to_s
            id = other.id
            break
          end
        end

        # If we still don't have an ID, generate a random one
        id = SecureRandom.uuid.gsub("-", "") if !id

        # Get a lock on this machine
        lock_file = lock_machine(id)
        if !lock_file
          raise "Failed to lock new machine: #{entry.name}"
        end

        @machine_locks[id] = lock_file
      end

      if !@machine_locks[id]
        raise "Unlocked write on machine: #{id}"
      end

      # Set our machine and save
      @machines[id] = struct
      unlocked_save
    end
  end

  Entry.new(id, struct)
end