triton-ops 0.18.4.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.org +40 -0
- data/bin/triton-ops-collect +76 -0
- data/bin/triton-ops-report +93 -0
- data/lib/triton-ops/resource/image.rb +195 -0
- data/lib/triton-ops/resource/platform.rb +81 -0
- data/lib/triton-ops/resource/server.rb +188 -0
- data/lib/triton-ops/resource/user.rb +206 -0
- data/lib/triton-ops/resource/virtual_machine.rb +298 -0
- data/lib/triton-ops/resource/virtual_machine/disk.rb +123 -0
- data/lib/triton-ops/resource/virtual_machine/network_interface.rb +119 -0
- data/lib/triton-ops/resource/vm.rb +14 -0
- data/lib/triton-ops/resources.rb +11 -0
- data/lib/triton-ops/snapshot.rb +56 -0
- data/lib/triton-ops/snapshot/catalog.rb +42 -0
- data/lib/triton-ops/snapshot/collector.rb +96 -0
- data/lib/triton-ops/snapshot/explorer.rb +70 -0
- data/lib/triton-ops/snapshot/reporter.rb +99 -0
- data/lib/triton-ops/snapshots.rb +8 -0
- data/lib/triton-ops/support/feature/comparable_as_hash.rb +14 -0
- data/lib/triton-ops/support/feature/hash_from_initialization_contract.rb +25 -0
- data/lib/triton-ops/support/type_coercion.rb +70 -0
- data/lib/triton-ops/support/types.rb +20 -0
- data/tests/exercise +70 -0
- data/tests/run +70 -0
- data/triton-ops.gemspec +21 -0
- metadata +192 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'redis' # MIT License
|
5
|
+
require 'tty-prompt' # MIT License
|
6
|
+
require_relative 'catalog'
|
7
|
+
|
8
|
+
module TritonOps
|
9
|
+
class Snapshot
|
10
|
+
class Explorer
|
11
|
+
def initialize(**options)
|
12
|
+
@namespace = options.fetch(:namespace) { '' }
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def headnode
|
17
|
+
return unless headnodes?
|
18
|
+
prompt.select 'Which headnode?', catalog.headnodes
|
19
|
+
end
|
20
|
+
|
21
|
+
def snapshot(headnode)
|
22
|
+
return unless snapshots? headnode
|
23
|
+
prompt.select 'Which snapshot?', catalog.snapshots(headnode)
|
24
|
+
end
|
25
|
+
|
26
|
+
def browse
|
27
|
+
h = headnode
|
28
|
+
s = snapshot h
|
29
|
+
[h, s]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def fatal(message, status = 1)
|
35
|
+
error message
|
36
|
+
exit status
|
37
|
+
end
|
38
|
+
|
39
|
+
def error(message)
|
40
|
+
pastel.red message
|
41
|
+
end
|
42
|
+
|
43
|
+
def headnodes?
|
44
|
+
not catalog.headnodes.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
def snapshots?(headnode)
|
48
|
+
not catalog.snapshots(headnode).empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def pastel
|
52
|
+
@pastel ||= Pastel.new
|
53
|
+
end
|
54
|
+
|
55
|
+
def catalog
|
56
|
+
@catalog ||= Catalog.new namespace: @namespace
|
57
|
+
end
|
58
|
+
|
59
|
+
def prompt
|
60
|
+
@prompt ||= ::TTY::Prompt.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def redis
|
64
|
+
@redis ||= ::Redis.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
E = TritonOps::Snapshot::Explorer.new
|
@@ -0,0 +1,99 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
require 'tty-table' # MIT License
|
6
|
+
|
7
|
+
require_relative '../snapshot'
|
8
|
+
|
9
|
+
module TritonOps
|
10
|
+
class Snapshot
|
11
|
+
class Reporter
|
12
|
+
include ::Contracts::Core
|
13
|
+
include ::Contracts::Builtin
|
14
|
+
|
15
|
+
Format = Enum[*%i(basic ascii unicode)]
|
16
|
+
|
17
|
+
Contract ::TritonOps::Snapshot, KeywordArgs[
|
18
|
+
:exclude => Optional[ArrayOf[String]],
|
19
|
+
:format => Optional[Format],
|
20
|
+
:targets => Optional[ArrayOf[String]],
|
21
|
+
] => Reporter
|
22
|
+
def initialize(snapshot, **options)
|
23
|
+
@snapshot = snapshot
|
24
|
+
@options = options
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
Contract None => Any
|
29
|
+
def usage
|
30
|
+
users.each do |user|
|
31
|
+
uuid = user.uuid
|
32
|
+
ram = memory(uuid) || 0
|
33
|
+
disk = storage(uuid) || 0
|
34
|
+
next if [ram, disk].all?(&:zero?)
|
35
|
+
|
36
|
+
login = user.login
|
37
|
+
cn = user.cn
|
38
|
+
|
39
|
+
puts "# #{login} (#{cn}) has (#{ram} MB of Memory) and (#{disk} MB of Storage) reserved"
|
40
|
+
|
41
|
+
puts TTY::Table.new(
|
42
|
+
header: ['VM Alias', 'Memory', 'Storage', 'Creation Date'],
|
43
|
+
rows: owned_by(uuid)
|
44
|
+
.sort_by { |vm| vm.create_timestamp }
|
45
|
+
.map do |vm|
|
46
|
+
[
|
47
|
+
vm.alias,
|
48
|
+
vm.max_physical_memory,
|
49
|
+
(vm.disks ? vm.disks.map { |disk| disk.size }.reduce(:+) : 0),
|
50
|
+
vm.create_timestamp.iso8601
|
51
|
+
]
|
52
|
+
end).render(format)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def format
|
59
|
+
@options.fetch(:format) { :basic }
|
60
|
+
end
|
61
|
+
|
62
|
+
def exclusions
|
63
|
+
@options.fetch(:exclude) { [] }
|
64
|
+
end
|
65
|
+
|
66
|
+
def targets
|
67
|
+
@options.fetch(:targets) { @snapshots.users.map { |user| user.login } }
|
68
|
+
end
|
69
|
+
|
70
|
+
def users
|
71
|
+
@snapshot.users
|
72
|
+
.reject { |user| exclusions.include? user.login }
|
73
|
+
.reject { |user| @snapshot.vms.none? { |vm| vm.owner_uuid == user.uuid } }
|
74
|
+
.select { |user| targets.empty? or targets.include?(user.login) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def owned_by(owner_uuid)
|
78
|
+
@snapshot
|
79
|
+
.vms
|
80
|
+
.select { |vm| vm.owner_uuid == owner_uuid }
|
81
|
+
end
|
82
|
+
|
83
|
+
def memory(owner_uuid)
|
84
|
+
owned_by(owner_uuid)
|
85
|
+
.map { |vm| vm.max_physical_memory }
|
86
|
+
.reduce(:+)
|
87
|
+
end
|
88
|
+
|
89
|
+
def storage(owner_uuid)
|
90
|
+
owned_by(owner_uuid)
|
91
|
+
.reject { |vm| vm.disks.empty? }
|
92
|
+
.map { |vm| vm.disks
|
93
|
+
.map { |disk| disk.size }
|
94
|
+
.reduce(:+) }
|
95
|
+
.reduce(:+)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
|
6
|
+
module TritonOps
|
7
|
+
module Support
|
8
|
+
module Feature
|
9
|
+
module HashFromInitializationContract
|
10
|
+
include ::Contracts::Core
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
self.class.__contracts_engine
|
14
|
+
.decorated_methods_for(:instance_methods, :initialize)
|
15
|
+
.flat_map(&:args_contracts)
|
16
|
+
.flat_map(&:keys)
|
17
|
+
.map { |k| [k, public_send(k)] }
|
18
|
+
.reject { |_, v| v.nil? }
|
19
|
+
.map { |k, v| [k, (v.respond_to?(:iso8601) ? v.iso8601 : v)] }
|
20
|
+
.to_h
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
require 'date' # Ruby Standard Library
|
6
|
+
require 'time' # Ruby Standard Library
|
7
|
+
|
8
|
+
module TritonOps
|
9
|
+
module Support
|
10
|
+
module TypeCoercion
|
11
|
+
module Coerce
|
12
|
+
include ::Contracts::Core
|
13
|
+
include ::Contracts::Builtin
|
14
|
+
|
15
|
+
Contract String => Time
|
16
|
+
def self.to_time(string)
|
17
|
+
if string =~ /^[[:digit:]]+$/
|
18
|
+
self.to_time string.chars.take(10).join.to_i
|
19
|
+
else
|
20
|
+
Time.parse(string).utc
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Contract Integer => Time
|
25
|
+
def self.to_time(integer)
|
26
|
+
if integer.digits.length > 10
|
27
|
+
self.to_time integer.to_s
|
28
|
+
else
|
29
|
+
Time.at(integer).utc
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Contract DateTime => Time
|
34
|
+
def self.to_time(datetime)
|
35
|
+
datetime.to_time
|
36
|
+
end
|
37
|
+
|
38
|
+
Contract Time => Time
|
39
|
+
def self.to_time(time)
|
40
|
+
time
|
41
|
+
end
|
42
|
+
|
43
|
+
Contract Enum[%w(true false)] => Bool
|
44
|
+
def self.to_bool(string)
|
45
|
+
case string
|
46
|
+
when 'true'
|
47
|
+
true
|
48
|
+
when 'false'
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Contract Bool => Bool
|
54
|
+
def self.to_bool(bool)
|
55
|
+
bool
|
56
|
+
end
|
57
|
+
|
58
|
+
Contract Time => String
|
59
|
+
def self.to_string(time)
|
60
|
+
time.utc.iso8601
|
61
|
+
end
|
62
|
+
|
63
|
+
Contract RespondTo[:to_s] => String
|
64
|
+
def self.to_string(object)
|
65
|
+
object.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
require 'date' # Ruby Standard Library
|
6
|
+
require 'time' # Ruby Standard Library
|
7
|
+
|
8
|
+
module TritonOps
|
9
|
+
module Support
|
10
|
+
module Types
|
11
|
+
module Castable
|
12
|
+
Time = ::Contracts::Or[::String, ::Integer, ::DateTime, ::Time]
|
13
|
+
Bool = ::Contracts::Or[::Contracts::Enum[*%w(true false)], ::TrueClass, ::FalseClass]
|
14
|
+
String = ::Contracts::RespondTo[:to_s]
|
15
|
+
end
|
16
|
+
|
17
|
+
CompressionAlgorithm = ::Contracts::Enum[*(%w(on off gzip lz4 lzjb zle) + (1..9).map { |n| "gzip-#{n}" })]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/tests/exercise
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require_relative '../lib/triton-ops/snapshot/explorer'
|
5
|
+
|
6
|
+
#############
|
7
|
+
# Constants #
|
8
|
+
#############
|
9
|
+
|
10
|
+
SUCCESS = 0
|
11
|
+
|
12
|
+
########################
|
13
|
+
# Environment Handling #
|
14
|
+
########################
|
15
|
+
|
16
|
+
feature = {
|
17
|
+
interactive: ENV.fetch('INTERACTIVE') { STDIN.tty? } != 'false',
|
18
|
+
namespace: ENV.fetch('NAMESPACE') { '' },
|
19
|
+
}
|
20
|
+
|
21
|
+
#####################
|
22
|
+
# Argument Handling #
|
23
|
+
#####################
|
24
|
+
|
25
|
+
begin
|
26
|
+
interactive = ::TritonOps::Snapshot::Explorer.new namespace: feature[:namespace]
|
27
|
+
headnode = ARGV.shift || (interactive.headnode if feature[:interactive])
|
28
|
+
timestamp = ARGV.shift || (interactive.snapshot headnode if feature[:interactive])
|
29
|
+
rescue Interrupt, TTY::Reader::InputInterrupt
|
30
|
+
exit Errno::EINTR::Errno
|
31
|
+
end
|
32
|
+
|
33
|
+
#################
|
34
|
+
# Sanity Checks #
|
35
|
+
#################
|
36
|
+
|
37
|
+
exit Errno::EINVAL::Errno if [headnode, timestamp].any?(&:nil?)
|
38
|
+
|
39
|
+
################
|
40
|
+
# Main Program #
|
41
|
+
################
|
42
|
+
|
43
|
+
# Where possible, the library provides Contracts for public interfaces. If a
|
44
|
+
# suitable Contract cannot be provided, then devising a sensible test is likely
|
45
|
+
# difficult.
|
46
|
+
|
47
|
+
# First, we begin with a Snapshot:
|
48
|
+
|
49
|
+
SNAPSHOT = TritonOps::Snapshot.new headnode, timestamp
|
50
|
+
|
51
|
+
# Then, for each major property of the snapshot, we convert each instance of a
|
52
|
+
# given type into a Hash, which implicitly validates all return Contracts.
|
53
|
+
#
|
54
|
+
# Each Hash is then used to instantiate a new Object of the original type,
|
55
|
+
# implicitly validating the initialization Contract.
|
56
|
+
|
57
|
+
SNAPSHOT.images.map(&:to_h).map { |image| ::TritonOps::Resource::Image.new image }
|
58
|
+
SNAPSHOT.platforms.map(&:to_h).map { |platform| ::TritonOps::Resource::Platform.new platform }
|
59
|
+
SNAPSHOT.servers.map(&:to_h).map { |server| ::TritonOps::Resource::Server.new server }
|
60
|
+
SNAPSHOT.users.map(&:to_h).map { |user| ::TritonOps::Resource::User.new user }
|
61
|
+
SNAPSHOT.vms.map(&:to_h).map { |vm| ::TritonOps::Resource::VirtualMachine.new vm }
|
62
|
+
|
63
|
+
# In a few cases, retrieval is slightly difference, but the concept is the same.
|
64
|
+
|
65
|
+
SNAPSHOT.vms.flat_map(&:disks).map(&:to_h).map { |disk| ::TritonOps::Resource::VirtualMachine::Disk.new disk }
|
66
|
+
SNAPSHOT.vms.flat_map(&:nics).map(&:to_h).map { |nic| ::TritonOps::Resource::VirtualMachine::NetworkInterface.new nic }
|
67
|
+
|
68
|
+
# If we've made it this far, all Contracts have passed.
|
69
|
+
|
70
|
+
exit SUCCESS
|
data/tests/run
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require_relative '../lib/triton-ops/snapshot/explorer'
|
5
|
+
|
6
|
+
#############
|
7
|
+
# Constants #
|
8
|
+
#############
|
9
|
+
|
10
|
+
SUCCESS = 0
|
11
|
+
|
12
|
+
########################
|
13
|
+
# Environment Handling #
|
14
|
+
########################
|
15
|
+
|
16
|
+
feature = {
|
17
|
+
interactive: ENV.fetch('INTERACTIVE') { STDIN.tty? } != 'false',
|
18
|
+
namespace: ENV.fetch('NAMESPACE') { '' },
|
19
|
+
}
|
20
|
+
|
21
|
+
#####################
|
22
|
+
# Argument Handling #
|
23
|
+
#####################
|
24
|
+
|
25
|
+
begin
|
26
|
+
interactive = ::TritonOps::Snapshot::Explorer.new namespace: feature[:namespace]
|
27
|
+
headnode = ARGV.shift || (interactive.headnode if feature[:interactive])
|
28
|
+
timestamp = ARGV.shift || (interactive.snapshot headnode if feature[:interactive])
|
29
|
+
rescue Interrupt, TTY::Reader::InputInterrupt
|
30
|
+
exit Errno::EINTR::Errno
|
31
|
+
end
|
32
|
+
|
33
|
+
#################
|
34
|
+
# Sanity Checks #
|
35
|
+
#################
|
36
|
+
|
37
|
+
exit Errno::EINVAL::Errno if [headnode, timestamp].any?(&:nil?)
|
38
|
+
|
39
|
+
################
|
40
|
+
# Main Program #
|
41
|
+
################
|
42
|
+
#
|
43
|
+
# Where possible, the library provides Contracts for public interfaces. If a
|
44
|
+
# suitable Contract cannot be provided, then devising a sensible test is likely
|
45
|
+
# difficult.
|
46
|
+
#
|
47
|
+
# First, we begin with a Snapshot:
|
48
|
+
|
49
|
+
SNAPSHOT = TritonOps::Snapshot.new headnode, timestamp
|
50
|
+
|
51
|
+
# Then, for each major property of the snapshot, we convert each instance of a
|
52
|
+
# given type into a Hash, which implicitly validates all return Contracts.
|
53
|
+
#
|
54
|
+
# Each Hash is then used to instantiate a new Object of the original type,
|
55
|
+
# implicitly validating the initialization Contract.
|
56
|
+
|
57
|
+
SNAPSHOT.images.map(&:to_h).map { |image| ::TritonOps::Resource::Image.new image }
|
58
|
+
SNAPSHOT.platforms.map(&:to_h).map { |platform| ::TritonOps::Resource::Platform.new platform }
|
59
|
+
SNAPSHOT.servers.map(&:to_h).map { |server| ::TritonOps::Resource::Server.new server }
|
60
|
+
SNAPSHOT.users.map(&:to_h).map { |user| ::TritonOps::Resource::User.new user }
|
61
|
+
SNAPSHOT.vms.map(&:to_h).map { |vm| ::TritonOps::Resource::VirtualMachine.new vm }
|
62
|
+
|
63
|
+
# In a few cases, retrieval is slightly difference, but the concept is the same.
|
64
|
+
|
65
|
+
SNAPSHOT.vms.flat_map(&:disks).map(&:to_h).map { |disk| ::TritonOps::Resource::VirtualMachine::Disk.new disk }
|
66
|
+
SNAPSHOT.vms.flat_map(&:nics).map(&:to_h).map { |nic| ::TritonOps::Resource::VirtualMachine::NetworkInterface.new nic }
|
67
|
+
|
68
|
+
# If we've made it this far, all Contracts have passed.
|
69
|
+
|
70
|
+
exit SUCCESS
|