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,123 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
|
6
|
+
require_relative '../../support/feature/comparable_as_hash'
|
7
|
+
require_relative '../../support/feature/hash_from_initialization_contract'
|
8
|
+
require_relative '../../support/types'
|
9
|
+
|
10
|
+
module TritonOps
|
11
|
+
module Resource
|
12
|
+
class VirtualMachine
|
13
|
+
class Disk
|
14
|
+
include ::Contracts::Core
|
15
|
+
include ::Contracts::Builtin
|
16
|
+
include ::TritonOps::Support::Feature::ComparableAsHash
|
17
|
+
include ::TritonOps::Support::Feature::HashFromInitializationContract
|
18
|
+
include ::TritonOps::Support::Types
|
19
|
+
|
20
|
+
Media = ::Contracts::Enum[*%w(disk cdrom)]
|
21
|
+
Model = ::Contracts::Enum[*%w(virtio ide scsi)]
|
22
|
+
|
23
|
+
Contract ({
|
24
|
+
:block_size => Integer,
|
25
|
+
:compression => CompressionAlgorithm,
|
26
|
+
:media => Media,
|
27
|
+
:model => Model,
|
28
|
+
:path => String,
|
29
|
+
:refreservation => Integer,
|
30
|
+
:size => Integer,
|
31
|
+
:zfs_filesystem => String,
|
32
|
+
:zpool => String,
|
33
|
+
:boot => Maybe[Bool],
|
34
|
+
:image_size => Maybe[Integer],
|
35
|
+
:image_uuid => Maybe[String],
|
36
|
+
}) => Disk
|
37
|
+
def initialize(**options)
|
38
|
+
@options = options
|
39
|
+
self.to_h
|
40
|
+
remove_instance_variable '@options'
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
#######################
|
45
|
+
# Required Properties #
|
46
|
+
#######################
|
47
|
+
|
48
|
+
Contract None => Integer
|
49
|
+
def block_size
|
50
|
+
@block_size ||= @options.fetch :block_size
|
51
|
+
end
|
52
|
+
|
53
|
+
Contract None => CompressionAlgorithm
|
54
|
+
def compression
|
55
|
+
@compression ||= @options.fetch :compression
|
56
|
+
end
|
57
|
+
|
58
|
+
Contract None => Media
|
59
|
+
def media
|
60
|
+
@media ||= @options.fetch :media
|
61
|
+
end
|
62
|
+
|
63
|
+
Contract None => Model
|
64
|
+
def model
|
65
|
+
@model ||= @options.fetch :model
|
66
|
+
end
|
67
|
+
|
68
|
+
Contract None => String
|
69
|
+
def path
|
70
|
+
@path ||= @options.fetch :path
|
71
|
+
end
|
72
|
+
|
73
|
+
Contract None => Integer
|
74
|
+
def refreservation
|
75
|
+
@refreservation ||= @options.fetch :refreservation
|
76
|
+
end
|
77
|
+
|
78
|
+
Contract None => Integer
|
79
|
+
def size
|
80
|
+
@size ||= @options.fetch :size
|
81
|
+
end
|
82
|
+
|
83
|
+
Contract None => String
|
84
|
+
def zfs_filesystem
|
85
|
+
@zfs_filesystem ||= @options.fetch :zfs_filesystem
|
86
|
+
end
|
87
|
+
|
88
|
+
Contract None => String
|
89
|
+
def zpool
|
90
|
+
@zpool ||= @options.fetch :zpool
|
91
|
+
end
|
92
|
+
|
93
|
+
#######################
|
94
|
+
# Optional Properties #
|
95
|
+
#######################
|
96
|
+
|
97
|
+
Contract None => Maybe[Bool]
|
98
|
+
def boot
|
99
|
+
@boot ||= (@options || {}).fetch(:boot, nil)
|
100
|
+
end
|
101
|
+
|
102
|
+
Contract None => Maybe[Integer]
|
103
|
+
def image_size
|
104
|
+
@image_size ||= (@options || {}).fetch(:image_size, nil)
|
105
|
+
end
|
106
|
+
|
107
|
+
Contract None => Maybe[String]
|
108
|
+
def image_uuid
|
109
|
+
@image_uuid ||= (@options || {}).fetch(:image_uuid, nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
#######################
|
113
|
+
# Convenience Methods #
|
114
|
+
#######################
|
115
|
+
|
116
|
+
Contract None => Bool
|
117
|
+
def image?
|
118
|
+
image_uuid ? true : false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
|
6
|
+
require_relative '../../support/feature/comparable_as_hash'
|
7
|
+
require_relative '../../support/feature/hash_from_initialization_contract'
|
8
|
+
require_relative '../../support/types'
|
9
|
+
|
10
|
+
module TritonOps
|
11
|
+
module Resource
|
12
|
+
class VirtualMachine
|
13
|
+
class NetworkInterface
|
14
|
+
include ::Contracts::Core
|
15
|
+
include ::Contracts::Builtin
|
16
|
+
include ::TritonOps::Support::Feature::ComparableAsHash
|
17
|
+
include ::TritonOps::Support::Feature::HashFromInitializationContract
|
18
|
+
include ::TritonOps::Support::Types
|
19
|
+
|
20
|
+
Model = ::Contracts::Enum[*%w(virtio e1000 rtl8139)]
|
21
|
+
|
22
|
+
Contract ({
|
23
|
+
:interface => String,
|
24
|
+
:ip => String,
|
25
|
+
:ips => ArrayOf[String],
|
26
|
+
:mac => String,
|
27
|
+
:nic_tag => String,
|
28
|
+
:gateway => Maybe[String],
|
29
|
+
:gateways => Maybe[ArrayOf[String]],
|
30
|
+
:model => Maybe[Model],
|
31
|
+
:mtu => Maybe[Integer],
|
32
|
+
:netmask => Maybe[String],
|
33
|
+
:network_uuid => Maybe[String],
|
34
|
+
:primary => Maybe[Bool],
|
35
|
+
:vlan_id => Maybe[Integer],
|
36
|
+
}) => NetworkInterface
|
37
|
+
def initialize(**options)
|
38
|
+
@options = options
|
39
|
+
self.to_h
|
40
|
+
remove_instance_variable '@options'
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
#######################
|
45
|
+
# Required Properties #
|
46
|
+
#######################
|
47
|
+
|
48
|
+
Contract None => String
|
49
|
+
def interface
|
50
|
+
@interface ||= @options.fetch :interface
|
51
|
+
end
|
52
|
+
|
53
|
+
Contract None => String
|
54
|
+
def ip
|
55
|
+
@ip ||= @options.fetch :ip
|
56
|
+
end
|
57
|
+
|
58
|
+
Contract None => ArrayOf[String]
|
59
|
+
def ips
|
60
|
+
@ips ||= @options.fetch :ips
|
61
|
+
end
|
62
|
+
|
63
|
+
Contract None => String
|
64
|
+
def mac
|
65
|
+
@mac ||= @options.fetch :mac
|
66
|
+
end
|
67
|
+
|
68
|
+
Contract None => String
|
69
|
+
def nic_tag
|
70
|
+
@nic_tag ||= @options.fetch :nic_tag
|
71
|
+
end
|
72
|
+
|
73
|
+
#######################
|
74
|
+
# Optional Properties #
|
75
|
+
#######################
|
76
|
+
|
77
|
+
Contract None => Maybe[String]
|
78
|
+
def gateway
|
79
|
+
@gateway ||= (@options || {}).fetch(:gateway, nil)
|
80
|
+
end
|
81
|
+
|
82
|
+
Contract None => ArrayOf[String]
|
83
|
+
def gateways
|
84
|
+
@gateways ||= @options.fetch(:gateways, [])
|
85
|
+
end
|
86
|
+
|
87
|
+
Contract None => Maybe[Model]
|
88
|
+
def model
|
89
|
+
@model ||= (@options || {}).fetch(:model, nil)
|
90
|
+
end
|
91
|
+
|
92
|
+
Contract None => Maybe[Integer]
|
93
|
+
def mtu
|
94
|
+
@mtu ||= (@options || {}).fetch(:mtu, nil)
|
95
|
+
end
|
96
|
+
|
97
|
+
Contract None => Maybe[String]
|
98
|
+
def netmask
|
99
|
+
@netmask ||= (@options || {}).fetch(:netmask, nil)
|
100
|
+
end
|
101
|
+
|
102
|
+
Contract None => Maybe[String]
|
103
|
+
def network_uuid
|
104
|
+
@network_uuid ||= (@options || {}).fetch(:network_uuid, nil)
|
105
|
+
end
|
106
|
+
|
107
|
+
Contract None => Maybe[Bool]
|
108
|
+
def primary
|
109
|
+
@primary ||= (@options || {}).fetch(:primary, nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
Contract None => Maybe[Integer]
|
113
|
+
def vlan_id
|
114
|
+
@vlan_id ||= (@options || {}).fetch(:vlan_id, nil)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require_relative 'resource/image'
|
5
|
+
require_relative 'resource/platform'
|
6
|
+
require_relative 'resource/server'
|
7
|
+
require_relative 'resource/user'
|
8
|
+
require_relative 'resource/virtual_machine'
|
9
|
+
require_relative 'resource/virtual_machine/disk'
|
10
|
+
require_relative 'resource/virtual_machine/network_interface'
|
11
|
+
require_relative 'resource/vm'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
require 'redis' # MIT License
|
6
|
+
require 'json' # Ruby Standard Library
|
7
|
+
|
8
|
+
require_relative 'support/type_coercion'
|
9
|
+
require_relative 'resources'
|
10
|
+
|
11
|
+
module TritonOps
|
12
|
+
class Snapshot
|
13
|
+
include ::Contracts::Core
|
14
|
+
include ::Contracts::Builtin
|
15
|
+
include ::TritonOps::Support::TypeCoercion
|
16
|
+
|
17
|
+
def initialize(headnode, timestamp, **options)
|
18
|
+
@headnode = headnode
|
19
|
+
@timestamp = Coerce.to_time timestamp
|
20
|
+
@namespace = options.fetch(:namespace) { '' }
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :headnode, :timestamp
|
25
|
+
|
26
|
+
def prefix
|
27
|
+
@prefix ||= "#{@namespace}/headnode/#{headnode}/snapshot/#{timestamp.iso8601}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def array_from_json_at_redis_prefix
|
31
|
+
raise TypeError unless type = ::TritonOps::Resource.constants.find { |name| name.to_s.downcase == __callee__.to_s.chop }
|
32
|
+
|
33
|
+
if instance_variable_defined? "@#{__callee__}"
|
34
|
+
instance_variable_get "@#{__callee__}"
|
35
|
+
else
|
36
|
+
instance_variable_set(
|
37
|
+
"@#{__callee__}",
|
38
|
+
JSON.parse(
|
39
|
+
(redis.get("#{prefix}/#{__callee__}") || '[]'),
|
40
|
+
symbolize_names: true).map { |spec| ::TritonOps::Resource.const_get(type).new spec })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
alias images array_from_json_at_redis_prefix
|
45
|
+
alias platforms array_from_json_at_redis_prefix
|
46
|
+
alias servers array_from_json_at_redis_prefix
|
47
|
+
alias users array_from_json_at_redis_prefix
|
48
|
+
alias vms array_from_json_at_redis_prefix
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def redis
|
53
|
+
@redis ||= Redis.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'redis' # MIT License
|
5
|
+
require_relative '../snapshot'
|
6
|
+
|
7
|
+
module TritonOps
|
8
|
+
class Snapshot
|
9
|
+
class Catalog
|
10
|
+
def initialize(**options)
|
11
|
+
@namespace = options.fetch(:namespace) { '' }
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :namespace
|
16
|
+
|
17
|
+
def headnodes
|
18
|
+
redis.smembers("#{namespace}/headnodes") || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def snapshots(headnode)
|
22
|
+
return [] unless headnodes.include? headnode
|
23
|
+
redis.smembers("#{namespace}/headnode/#{headnode}/snapshots") || []
|
24
|
+
end
|
25
|
+
|
26
|
+
def properties(headnode, snapshot)
|
27
|
+
return [] unless snapshots(headnode).include? snapshot
|
28
|
+
redis.smembers("#{namespace}/headnode/#{headnode}/snapshot/#{snapshot}") || []
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(headnode, snapshot, property = :all)
|
32
|
+
TritonOps::Snapshot.new headnode, snapshot, namespace: @namespace
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def redis
|
38
|
+
@redis ||= Redis.new
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'contracts' # BSD-2-Clause License
|
5
|
+
require 'json' # Ruby Standard Library
|
6
|
+
require 'redis' # MIT License
|
7
|
+
require 'time' # Ruby Standard Library
|
8
|
+
|
9
|
+
module TritonOps
|
10
|
+
class Snapshot
|
11
|
+
class Collector
|
12
|
+
include ::Contracts::Core
|
13
|
+
include ::Contracts::Builtin
|
14
|
+
|
15
|
+
Contract String, KeywordArgs[namespace: Optional[String]] => Collector
|
16
|
+
def initialize(headnode, **options)
|
17
|
+
@headnode = headnode
|
18
|
+
@namespace = options.fetch(:namespace) { '' }
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :headnode
|
23
|
+
attr_accessor :namespace
|
24
|
+
|
25
|
+
COMMANDS = {
|
26
|
+
images: %q(/opt/smartdc/bin/sdc-imgadm list --json),
|
27
|
+
platforms: %q(/opt/smartdc/bin/sdcadm platform list --json),
|
28
|
+
servers: %q(/opt/smartdc/bin/sdc-server lookup -j),
|
29
|
+
timestamp: %q(/usr/xpg4/bin/date +%s),
|
30
|
+
users: %q(/opt/smartdc/bin/sdc-useradm search --json 'uuid=*'),
|
31
|
+
vms: %q(/opt/smartdc/bin/sdc-vmadm list --json),
|
32
|
+
}
|
33
|
+
|
34
|
+
TARGETS = COMMANDS.keys - %i(timestamp)
|
35
|
+
|
36
|
+
Contract RespondTo[:to_s], String => Any
|
37
|
+
def record(key, value)
|
38
|
+
snapshot = timestamp.iso8601
|
39
|
+
redis.multi do
|
40
|
+
redis.set "#{namespace}/headnode/#{headnode}/snapshot/#{snapshot}/#{key}", value
|
41
|
+
redis.sadd "#{namespace}/headnode/#{headnode}/snapshot/#{snapshot}", key
|
42
|
+
redis.sadd "#{namespace}/headnode/#{headnode}/snapshots", snapshot
|
43
|
+
redis.sadd "#{namespace}/headnodes", headnode
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Contract RespondTo[:to_s], Any => Any
|
48
|
+
def record(key, value)
|
49
|
+
record key, JSON.dump(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Most of the commands we need to execute output a JSON array.
|
53
|
+
def array_from_json_output_of_remote_command
|
54
|
+
if instance_variable_defined? "@#{__callee__}"
|
55
|
+
instance_variable_get "@#{__callee__}"
|
56
|
+
else
|
57
|
+
instance_variable_set(
|
58
|
+
"@#{__callee__}",
|
59
|
+
JSON.parse(execute_remote!(command(__callee__))))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
alias images array_from_json_output_of_remote_command
|
64
|
+
alias platforms array_from_json_output_of_remote_command
|
65
|
+
alias servers array_from_json_output_of_remote_command
|
66
|
+
alias users array_from_json_output_of_remote_command
|
67
|
+
alias vms array_from_json_output_of_remote_command
|
68
|
+
|
69
|
+
# This one needs special handling, since it outputs one JSON object per line.
|
70
|
+
def servers
|
71
|
+
@servers ||= execute_remote!(command(:servers)).each_line.map { |line| JSON.parse line }
|
72
|
+
end
|
73
|
+
|
74
|
+
# This one needs special handling, since there is no JSON involved.
|
75
|
+
Contract None => Time
|
76
|
+
def timestamp
|
77
|
+
@timestamp ||= Time.at(
|
78
|
+
execute_remote!(command(:timestamp)).to_i).utc
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def command(target)
|
84
|
+
COMMANDS.fetch target
|
85
|
+
end
|
86
|
+
|
87
|
+
def execute_remote!(command)
|
88
|
+
%x(ssh #{headnode} -- #{command})
|
89
|
+
end
|
90
|
+
|
91
|
+
def redis
|
92
|
+
@redis ||= Redis.new
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|