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.
@@ -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