Class: Vagrant::MachineIndex

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/vagrant/machine_index.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", "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

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.



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/vagrant/machine_index.rb', line 45

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



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/vagrant/machine_index.rb', line 64

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.



92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/vagrant/machine_index.rb', line 92

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:



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

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)


149
150
151
152
153
154
155
156
# File 'lib/vagrant/machine_index.rb', line 149

def include?(uuid)
  @lock.synchronize do
    with_index_lock do
      unlocked_reload
      return !!find_by_prefix(uuid)
    end
  end
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:



166
167
168
169
170
# File 'lib/vagrant/machine_index.rb', line 166

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:



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

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