triton-ops 0.18.4.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,8 @@
1
+
2
+ # -*- ruby -*-
3
+
4
+ require_relative 'snapshot'
5
+ require_relative 'snapshot/catalog'
6
+ require_relative 'snapshot/collector'
7
+ require_relative 'snapshot/explorer'
8
+ require_relative 'snapshot/reporter'
@@ -0,0 +1,14 @@
1
+
2
+ # -*- ruby -*-
3
+
4
+ module TritonOps
5
+ module Support
6
+ module Feature
7
+ module ComparableAsHash
8
+ def ==(other)
9
+ (other.respond_to?(:to_h) && (self.to_h == other.to_h)) || false
10
+ end
11
+ end
12
+ end
13
+ end
14
+ 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