Skip to content

Instantly share code, notes, and snippets.

@csabahenk
Last active August 22, 2018 15:59
Show Gist options
  • Save csabahenk/9522305 to your computer and use it in GitHub Desktop.
Save csabahenk/9522305 to your computer and use it in GitHub Desktop.
vmtree -- an utility to display cloud images and servers' inheritance tree.

Synopsis

$ vmtree -h | head -1
Utility to display cloud images and servers' inheritance tree.

The nova client program (and the web UI) is a source of comprehensive information on an OpenStack deployment. This information, however, is presented in a strictly typed manner: you get information either on servers, or images, or networks, or whatever entity, but on one of these at a time.

Some information is though inherently heterogenous. One such is the inheritance tree, involving images and servers. Images beget servers by instantiation, and servers beget images by snapshotting. It might be convenient to get an overview of these begetting relations. vmtree is an utility to display the tree that they consitute.

Install

Requires ruby ≥ 1.9.2, and the colorize and fog gems:

# gem install fog colorize

Then run in place.

Usage

$ vmtree -h
Utility to display cloud images and servers' inheritance tree.
Usage: vmtree [options]
    -s, --set X                      set a fog parameter in K:V format
    -p, --password[=P]               specify password or read from stdin
    -i, --indent I                   indentation string (raw or json)
    -C, --color[=B]                  colorized output
    -a, --all-images                 show all images
        --all-tenants                show all tenants' servers
    -A, --attrs A                    list of attributes ("[ap1,ap2,..|]a1,a2,...")

Currently for OpenStack (being based on fog, a versatile cloud library, other cloud providers are feasible to support).

Identity and crendentials can be given in the following ways:

  1. novaclient style environment variables (usually provided by sourcing an openrc file);
  2. fog credential file (${FOGRC:=~/.fog});
  3. individual specfication of the parameters via --set
    • --password=<passwd> is a shorthand for --set openstack_api_key:<passwd>;
    • --password with no argument reads <passwd> from stdin.

1. is a nova compatibility mode but the set of supported parameters is limited. Parameters set as of 2.-3. are directly used by Fog::Compute.new (reference: Fog::Compute::OpenStack's requires/recognizes).

Examples

Color mode is automatic by default, ie. colorized on tty, monochromatic otherwise. In the following examples we are using -Cno for pastability but in real life you can omit that.

Basic usage:

$ vmtree -Cno
Image loot01
  Server loot01
    Image loot03
      Server marauder5
  Server loot00
  Server marauder4c
  Server marauder4b
    Image loot02
Image Fedora 20 official release
  Server marauder

Displaying some attributes along the items:

$ vmtree -Cno -A status,state,ip_addresses
Image loot01: ACTIVE
  Server loot01: ACTIVE ["172.16.53.19", "10.3.13.103"]
    Image loot03: ACTIVE
      Server marauder5: ACTIVE ["172.16.53.18", "10.3.8.100"]
  Server loot00: ACTIVE ["172.16.53.14", "10.3.11.4"]
  Server marauder4c: ACTIVE ["172.16.53.6", "10.3.8.7"]
  Server marauder4b: ACTIVE ["172.16.53.25", "10.3.8.116"]
    Image loot02: ACTIVE
Image Fedora 20 official release: ACTIVE
  Server marauder: ACTIVE ["172.16.53.3", "10.3.12.83"]

The following observations can be made:

  • For each item, the attribute set is filtered by endowment, ie. if you ask for non-extant attributes (-A foo,bar) you don't get an error, rather they will be silently discarded. In this case, ip_addresses is available only for servers, so it's displayed along them (but there is no issue with images not having it).
  • The available attributes are those of the Fog::Compute::OpenStack::{Image,Server} classes.
  • An oddity of fog is that status info is avaliable as status for images and state for servers. So to get the overall status info we have to pass status,state to -A.

Attributes can be put to prefix position with pipe character (useful for fixed width ones):

$ vmtree -Cno -A 'id,state,status|ip_addresses'
55a9cda8-f4ab-4c7c-9b6d-324fabc15b3c ACTIVE  Image loot01
51d96fc6-2681-4c7e-8267-593480364db7 ACTIVE    Server loot01: ["172.16.53.19", "10.3.13.103"]
732eba89-4980-4a39-b03e-16e007013457 ACTIVE      Image loot03
47f874dc-cec8-4f71-a3df-a743ada96e59 ACTIVE        Server marauder5: ["172.16.53.18", "10.3.8.100"]
065e08ff-9601-496e-9190-8f5536c16cd7 ACTIVE    Server loot00: ["172.16.53.14", "10.3.11.4"]
87fd2d01-f219-4df2-9124-ecae6ab45a28 ACTIVE    Server marauder4c: ["172.16.53.6", "10.3.8.7"]
d5d6be22-a173-48f6-b4bd-9654ba77d1f3 ACTIVE    Server marauder4b: ["172.16.53.25", "10.3.8.116"]
b88b0810-f342-4b8f-9d05-0a71855369f0 ACTIVE      Image loot02
20a6f54a-bd46-4160-9cf7-a1e2d2c230aa ACTIVE  Image Fedora 20 official release
12f8926b-39fd-44ac-8274-70939498db9c ACTIVE    Server marauder: ["172.16.53.3", "10.3.12.83"]

Asking for all images (as by default only instantiated ones are displayed):

$ vmtree -Cno -a
Image rh-atomic-controller-2014.2
Image rh-atomic-controller-2014.1
Image fedora-atomic-controller-2014.3
Image savanna-0.3-vanilla-1.2.1-fedora19
Image loot01
  Server loot01
    Image loot03
      Server marauder5
  Server loot00
  Server marauder4c
  Server marauder4b
    Image loot02
Image loot00
Image Fedora-x86_64-20-20131211.1 QCOW2
Image fedora-19-dev_jeos
Image centos-6.5-20140117.0.x86_64
Image centos-6-x86_64-cloud
Image ubuntu-12.04_jeos
Image fedora-20_jeos
Image fedora-19_jeos
Image RHEL 7.0 Public Beta (Official Guest Image)
Image Ubuntu 13.10
Image Fedora 20 official release
  Server marauder
Image rhel7-dogfood
Image rhel-server-x86_64-kvm-6.4_20130130.0-4
Image Fedora 19
Image Fedora19-RC1
Image Fedora18-x64
Image RHEL6.4-qcow2
#!/usr/bin/env ruby
require 'fog'
require 'fog/openstack/models/compute/image'
require 'fog/openstack/models/compute/server'
require 'colorize'
class Object
def attr_s attrs=[]
attrs.select { |a| respond_to? a }.map { |a| send(a).to_s }.join " "
end
end
class Hash
def cut *a
select { |k,v| a.include? k }
end
end
class Fog::Model
def repr opts={}
_name = name || '??'
opts[:color] ?
_name.colorize(self.class::COLOR) :
self.class.name.split("::")[-1] + " " + _name
end
class Stub < self
COLOR = :blue
def initialize id
@id = id
@name = '??'
@state = '------'
end
attr_reader :id, :parent_id, :name, :state
end
end
class Fog::Compute::OpenStack::Image
COLOR = :green
def parent_id
(server||{})["id"]
end
end
class Fog::Compute::OpenStack::Server
COLOR = :red
def parent_id
(image||{})["id"]
end
end
module VMTree
class Tree
IDT = " "
def initialize cs, flts={}
@id = {}
@chld = {}
{:servers => [{:filters => flts}], :images => []}.each { |m,a|
cs.send(m, *a).each { |e|
@id[e.id] = e
if e.parent_id
(@chld[e.parent_id]||=[]) << e.id
@id[e.parent_id] ||= Fog::Model::Stub.new e.parent_id
end
}
}
end
def roots opts={}
sel = proc do |i,e|
case e
when Fog::Compute::OpenStack::Server
true
when Fog::Model::Stub
@chld[i].size > 1 or begin
c = @chld[i].first
sel[c,@id[c]]
end
when Fog::Compute::OpenStack::Image
@chld[i]
else raise "unknown node class #{e.class}"
end
end
@id.select { |i,e|
(! @id[e.parent_id]) and (opts[:all] or sel[i,e])
}.keys
end
def show i, opts={}
opts = {:out=>$>, :depth=>0}.merge! opts
dat = [opts[:indent]*opts[:depth], @id[i].repr(opts)]
[[:attrs_pre, " ", [0, 1]],
[:attrs, ": ", [-1, -2]]
].each { |k, sep, posp|
as = @id[i].attr_s(opts[k] || [])
next if as.empty?
posp.zip([as, sep]).each { |pos,s|
dat.insert pos, s
}
}
dat << "\n"
dat.each { |s| opts[:out] << s }
end
def show_r i, opts={}
show i, opts
opts = opts.merge :depth => (opts[:depth]||0) + 1
(@chld[i]||[]).each { |ii|
show_r ii, opts
}
end
def showtree opts={}
opts = {:out=>$>, :depth=>0, :indent=>IDT, :all=>false}.merge! opts
roots(opts).each { |i| show_r i, opts }
nil
end
end
module Util
BASE_PARAMS = { provider: "openstack" }
ECONV = {
"OS_AUTH_URL" => :openstack_auth_url,
"OS_TENANT_NAME" => :openstack_tenant,
"OS_USERNAME" => :openstack_username,
"OS_PASSWORD" => :openstack_api_key
}
def get_params opts={}
params = BASE_PARAMS.dup
ENV.each { |k,v|
k = ECONV[k] or next
params[k] = case k
when :openstack_auth_url
# fix up the difference in auth url as used by fog and novaclient
File.join v, "tokens"
else
v
end
}
(opts[:params]||[]).each { |q|
k,v = q.split ":", 2
params[k.to_sym] = v
}
if opts.key? :pass
params[:openstack_api_key] = opts[:pass] || STDIN.noecho(&:gets).strip
end
params
end
end
extend Util
end
if __FILE__ == $0
require 'io/console'
require 'optparse'
require 'json'
opts = {:params => []}
fetching = proc { |q| proc { |v| opts[q] = v } }
OptionParser.new { |op|
op.banner = "Utility to display cloud images and servers' inheritance tree.\n#{op.banner}"
op.on('-s', '--set X', 'set a fog parameter in K:V format') { |v| opt[:params] << v }
op.on('-p', '--password[=P]', 'specify password or read from stdin', &fetching[:pass])
op.on('-i', '--indent I', 'indentation string (raw or json)', &fetching[:indent])
op.on('-C', '--color[=B]', 'colorized output', &fetching[:color])
op.on('-a', '--all-images', 'show all images') { opts[:all] = true }
op.on('--all-tenants', "show all tenants' servers") { opts[:all_tenants] = true }
op.on('-A', '--attrs A', 'list of attributes ("[ap1,ap2,..|]a1,a2,...")', &fetching[:attrs])
}.parse!
if (opts[:indent]||"")[0] == '"'
opts[:indent] = JSON.load opts[:indent]
String === opts[:indent] or raise TypeError, "--indent should be string"
end
if opts[:attrs]
aa = opts[:attrs].split("|", -1).map { |x| x.split "," }
case aa.size
when 1
aa.insert 0, []
when 2
else
raise "invalid --attrs"
end
opts[:attrs_pre], opts[:attrs] = aa
end
opts[:color] = case opts[:color]
when nil
STDOUT.isatty
when /\A(0|false|no)\Z/i
false
else
true
end
VMTree::Tree.new(Fog::Compute.new(VMTree.get_params(opts)), opts.cut(:all_tenants)).
showtree opts.cut(:all, :indent, :color, :attrs, :attrs_pre)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment