shu-san-scripts 0.2.2 → 0.2.3

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,103 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ # Volume names are based on RFC 4122 UUIDs
16
+ require "uuidtools"
17
+
18
+ # Use the ZFS library
19
+ require "SANStore/zfs/zfs"
20
+
21
+ # Use the COMStar iSCSI library
22
+ require "SANStore/iSCSI/comstar.rb"
23
+
24
+ module SANStore::CLI::Commands
25
+
26
+ # @author David Love
27
+ #
28
+ # The +list_vols+ command show the current iSCSI targets available
29
+ # on this host
30
+ class ListVols < Cri::Command
31
+
32
+ # The name of the sub-command (as it appears in the command line app)
33
+ def name
34
+ 'list_vols'
35
+ end
36
+
37
+ # The aliases this sub-command is known by
38
+ def aliases
39
+ [
40
+ "list_vol", "list", "ls"
41
+ ]
42
+ end
43
+
44
+ # A short help text describing the purpose of this command
45
+ def short_desc
46
+ 'Show the currently defined iSCSI targets on this host.'
47
+ end
48
+
49
+ # A longer description, detailing both the purpose and the
50
+ # use of this command
51
+ def long_desc
52
+ 'Displays a list of valid iSCSI targets, all of which ' +
53
+ 'should be available on the local network' + "\n\n"
54
+ 'NOTE: Because of the way iSCSI works, this host has no ' +
55
+ 'way of knowing what is actually '+ ANSI.bold{ "in" } + ' the volume. So even ' +
56
+ 'if a target is defined, this host has no way of knowing if ' +
57
+ 'a given initiator can actually ' + ANSI.bold{ "use" } +
58
+ 'the contents of this volume. If something appears to be '
59
+ 'wrong, check the set-up of the host to make sure it can '
60
+ 'actually connect to the targets defined here.'
61
+ end
62
+
63
+ # Show the user the basic syntax of this command
64
+ def usage
65
+ "store list_vols"
66
+ end
67
+
68
+ # Define the options for this command
69
+ def option_definitions
70
+ [
71
+ ]
72
+ end
73
+
74
+ # Execute the command
75
+ def run(options, arguments)
76
+
77
+ # Get the list of defined volumes
78
+ volumes = COMStar.list_vols
79
+
80
+ # Show the list to the caller
81
+ text = String.new
82
+ volumes.each{|target|
83
+ text << sprintf("%-60s ", target[:name])
84
+
85
+ if target[:state] == "online" then
86
+ text << sprintf("%-20s ", ANSI.green{ target[:state] })
87
+ else
88
+ text << sprintf("%-20s ", ANSI.black{ target[:state] })
89
+ end
90
+
91
+ if target[:sessions].to_i > 0 then
92
+ text << sprintf("%-10s\n", ANSI.white{ target[:sessions] })
93
+ else
94
+ text << sprintf("%-10s\n", ANSI.black{ target[:sessions] })
95
+ end
96
+ }
97
+ puts sprintf("%-68s %-19s %-10s\n", ANSI.bold{ "Target Name"}, ANSI.bold{ "Status" }, ANSI.bold{ "Open Sessions" })
98
+ puts text
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,134 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ # Volume names are based on RFC 4122 UUIDs
16
+ require "uuidtools"
17
+
18
+ # Use the ANSI library to report the status to the user
19
+ require "ansi/code"
20
+
21
+ # Use the Socket API to get the first IPv4 address
22
+ require "socket"
23
+
24
+ # Use the ZFS library
25
+ require "SANStore/zfs/zfs"
26
+
27
+ # Use the COMStar iSCSI library
28
+ require "SANStore/iSCSI/comstar.rb"
29
+
30
+ module SANStore::CLI::Commands
31
+
32
+ # @author David Love
33
+ #
34
+ # The +new_vol+ command adds a new ZFS volume to the specified volume
35
+ # store, and sets it up as an iSCSI target. Defaults are supplied to
36
+ # all arguments, but can be overridden by the user for slightly
37
+ # customised set-up of the volume store. However, the user interface
38
+ # should be kept as simple as possible.
39
+ class NewVol < Cri::Command
40
+
41
+ # The name of the sub-command (as it appears in the command line app)
42
+ def name
43
+ 'new_vol'
44
+ end
45
+
46
+ # The aliases this sub-command is known by
47
+ def aliases
48
+ [
49
+ "new", "add", "add_vol"
50
+ ]
51
+ end
52
+
53
+ # A short help text describing the purpose of this command
54
+ def short_desc
55
+ 'Create a new iSCSI volume in the SAN volume store.'
56
+ end
57
+
58
+ # A longer description, detailing both the purpose and the
59
+ # use of this command
60
+ def long_desc
61
+ 'By default, this command creates a 20G ZFS volume, and marks it for ' +
62
+ 'sharing as an iSCSI target on the local network.' + "\n\n" +
63
+ 'Warning: By default this commands sets up the iSCSI target with NO ' +
64
+ 'security. This is fine for testing and use in the labs, but obviously ' +
65
+ 'is not ideal if you care about the data stored on this new volume...'
66
+ end
67
+
68
+ # Show the user the basic syntax of this command
69
+ def usage
70
+ "store new_volume [--volume-store ZFS_PATH][--name GUID] [--size INTEGER]"
71
+ end
72
+
73
+ # Define the options for this command
74
+ def option_definitions
75
+ [
76
+ { :short => 'v', :long => 'volume_store', :argument => :optional,
77
+ :desc => 'specifify the ZFS root of the new iSCSI volume. Defaults to "store/volumes".'
78
+ },
79
+ { :short => 'n', :long => 'name', :argument => :optional,
80
+ :desc => 'the name of the new volume. This must be a valid ZFS volume name, and defaults to ' +
81
+ 'an RFC 4122 GUID.'
82
+ },
83
+ { :short => 's', :long => 'size', :argument => :optional,
84
+ :desc => 'the size of the new iSCSI volume. Note that while ZFS allows you to change the size ' +
85
+ 'of the new volume relatively easily, because the iSCSI initiator sees this volume as a raw ' +
86
+ 'device changing the size later may be very easy or very difficult depending on the initiators ' +
87
+ 'operating system (and the specific file system being used). In other words, choose with care: ' +
88
+ 'by default this command uses a size of 20G, which should be enough for most tasks in the labs.'
89
+ },
90
+ ]
91
+ end
92
+
93
+ # Execute the command
94
+ def run(options, arguments)
95
+
96
+ # Look at the options, and if we don't find any (or some are
97
+ # missing), set them to the default values
98
+ if options[:volume_store].nil? or options[:volume_store].empty? then
99
+ volume_store = "store/volumes"
100
+ options[:volume_store] = volume_store
101
+ end
102
+
103
+ if options[:name].nil? or options[:name].empty? then
104
+ name = UUIDTools::UUID.timestamp_create
105
+ options[:name] = name.to_s
106
+ end
107
+
108
+ if options[:size].nil? or options[:size].empty? then
109
+ size = "20G"
110
+ options[:size] = size
111
+ end
112
+
113
+ SANStore::CLI::Logger.instance.log_level(:low, :info, "Using #{options[:name]} as the volume identifier")
114
+
115
+ # Ask for a new volume
116
+ ZFS.new_volume(options[:volume_store] + "/" + options[:name], options[:size])
117
+
118
+ # Set the volume up as an iSCSI target
119
+ target_name = COMStar.new_target(options[:volume_store] + "/" + options[:name], options[:name])
120
+
121
+ # Tell the caller what the new volume name is
122
+ text = "\n"
123
+ text << "A new iSCSI target has been created with the following properties\n"
124
+ text << "\n"
125
+ text << sprintf(" %-25s %s\n", ANSI.red{ "Name:" }, target_name.wrap_and_indent(78, 20).lstrip)
126
+ text << sprintf(" %-25s %s\n", ANSI.red{ "IPv4 Address:" }, Socket::getaddrinfo(Socket.gethostname,"echo",Socket::AF_INET)[0][3])
127
+ text << "\n"
128
+
129
+ puts text
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,92 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+ #
15
+
16
+ require 'singleton'
17
+ require 'facets'
18
+
19
+ module SANStore::CLI
20
+
21
+ # SANStore::CLI::Logger is a singleton class responsible for generating
22
+ # feedback in the terminal.
23
+ class Logger
24
+
25
+ # ANSI console codes (escape sequences) for highlighting particular
26
+ # log outputs.
27
+ ACTION_COLORS = {
28
+ :create => "\e[1m" + "\e[32m", # bold + green
29
+ :delete => "\e[1m" + "\e[31m", # bold + red
30
+ :error => "\e[1m" + "\e[31m", # bold + red
31
+ :identical => "\e[1m" + "\e[34m", # bold + blue
32
+ :info => "\e[1m" + "\e[34m", # bold + blue
33
+ :update => "\e[1m" + "\e[33m", # bold + yellow
34
+ :warning => "\e[1m" + "\e[33m", # bold + yellow
35
+ }
36
+
37
+ include Singleton
38
+
39
+ # The log level, which can be :high, :low or :off (which will log all
40
+ # messages, only high-priority messages, or no messages at all,
41
+ # respectively).
42
+ attr_accessor :level
43
+
44
+ # Whether to use color in log messages or not
45
+ attr_accessor :color
46
+ alias_method :color?, :color
47
+
48
+ def initialize
49
+ @level = :high
50
+ @color = true
51
+ end
52
+
53
+ # Logs a messsage, using appropriate colours to highlight different
54
+ # levels.
55
+ #
56
+ # +level+:: The importance of this action. Can be :high or :low.
57
+ #
58
+ # +action+:: The kind of file action. Can be :create, :update or
59
+ # :identical.
60
+ #
61
+ # +message+:: The identifier of the item the action was performed on.
62
+ def log_level(level, action, message)
63
+ log(
64
+ level,
65
+ '%s%12s%s: %s' % [
66
+ color? ? ACTION_COLORS[action.to_sym] : '',
67
+ action.to_s.capitalize,
68
+ color? ? "\e[0m" : '',
69
+ message.word_wrap(60).indent(15).lstrip
70
+ ]
71
+ )
72
+ end
73
+
74
+ # Logs a message.
75
+ #
76
+ # +level+:: The importance of this message. Can be :high or :low.
77
+ #
78
+ # +message+:: The message to be logged.
79
+ #
80
+ # +io+:: The IO instance to which the message will be written. Defaults to
81
+ # standard output.
82
+ def log(level, message, io=$stdout)
83
+ # Don't log when logging is disabled
84
+ return if @level == :off
85
+
86
+ # Log when level permits it
87
+ io.puts(message) if (@level == :low or @level == level)
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,12 @@
1
+ module Cri
2
+
3
+ # The current Cri version.
4
+ CRI_VERSION = '1.0'
5
+
6
+ end
7
+
8
+ # Load Cri
9
+ require 'SANStore/cri/base'
10
+ require 'SANStore/cri/command'
11
+ require 'SANStore/cri/core_ext'
12
+ require 'SANStore/cri/option_parser'
@@ -0,0 +1,153 @@
1
+ module Cri
2
+
3
+ # Cri::Base is the central class representing a commandline tool. It has a
4
+ # list of commands.
5
+ class Base
6
+
7
+ # The CLI's list of commands (should also contain the help command)
8
+ attr_reader :commands
9
+
10
+ # The CLI's help command (required)
11
+ attr_accessor :help_command
12
+
13
+ # Creates a new instance of the commandline tool.
14
+ def initialize(tool_name)
15
+ @tool_name = tool_name
16
+
17
+ @commands = []
18
+ end
19
+
20
+ # Parses the given commandline arguments and executes the requested
21
+ # command.
22
+ def run(args)
23
+ # Check arguments
24
+ if args.length == 0
25
+ @help_command.run([], [])
26
+ exit 1
27
+ end
28
+
29
+ # Partition options
30
+ opts_before_command = []
31
+ command_name = nil
32
+ opts_and_args_after_command = []
33
+ stage = 0
34
+ args.each do |arg|
35
+ # Update stage if necessary
36
+ stage = 1 if stage == 0 && !is_option?(arg)
37
+
38
+ # Add
39
+ opts_before_command << arg if stage == 0
40
+ command_name = arg if stage == 1
41
+ opts_and_args_after_command << arg if stage == 2
42
+
43
+ # Update stage if necessary
44
+ stage = 2 if stage == 1
45
+ end
46
+
47
+ # Handle options before command
48
+ begin
49
+ parsed_arguments = Cri::OptionParser.parse(opts_before_command, global_option_definitions)
50
+ rescue Cri::OptionParser::IllegalOptionError => e
51
+ $stderr.puts "illegal option -- #{e}"
52
+ exit 1
53
+ end
54
+ parsed_arguments[:options].keys.each do |option|
55
+ handle_option(option)
56
+ end
57
+
58
+ # Get command
59
+ if command_name.nil?
60
+ $stderr.puts "no command given"
61
+ exit 1
62
+ end
63
+ command = command_named(command_name)
64
+ if command.nil?
65
+ $stderr.puts "no such command: #{command_name}"
66
+ exit 1
67
+ end
68
+
69
+ # Parse arguments
70
+ option_definitions = command.option_definitions + global_option_definitions
71
+ begin
72
+ parsed_arguments = Cri::OptionParser.parse(opts_and_args_after_command, option_definitions)
73
+ rescue Cri::OptionParser::IllegalOptionError => e
74
+ $stderr.puts "illegal option -- #{e}"
75
+ exit 1
76
+ rescue Cri::OptionParser::OptionRequiresAnArgumentError => e
77
+ $stderr.puts "option requires an argument -- #{e}"
78
+ exit 1
79
+ end
80
+
81
+ # Handle global options
82
+ global_options = global_option_definitions.map { |o| o[:long] }
83
+ global_options.delete_if { |o| !parsed_arguments[:options].keys.include?(o.to_sym) }
84
+ global_options.each { |o| handle_option(o.to_sym) }
85
+
86
+ if parsed_arguments[:options].has_key?(:help)
87
+ # Show help for this command
88
+ show_help(command)
89
+ else
90
+ # Run command
91
+ command.run(parsed_arguments[:options], parsed_arguments[:arguments])
92
+ end
93
+ end
94
+
95
+ # Returns the command with the given name.
96
+ def command_named(name)
97
+ # Find by exact name or alias
98
+ command = @commands.find { |c| c.name == name or c.aliases.include?(name) }
99
+ return command unless command.nil?
100
+
101
+ # Find by approximation
102
+ commands = @commands.select { |c| c.name[0, name.length] == name }
103
+ if commands.length > 1
104
+ $stderr.puts "#{@tool_name}: '#{name}' is ambiguous:"
105
+ $stderr.puts " #{commands.map { |c| c.name }.join(' ') }"
106
+ exit 1
107
+ elsif commands.length == 0
108
+ $stderr.puts "#{@tool_name}: unknown command '#{name}'\n"
109
+ show_help
110
+ exit 1
111
+ else
112
+ return commands[0]
113
+ end
114
+ end
115
+
116
+ # Shows the help text for the given command, or shows the general help
117
+ # text if no command is given.
118
+ def show_help(command=nil)
119
+ if command.nil?
120
+ @help_command.run([], [])
121
+ else
122
+ @help_command.run([], [ command.name ])
123
+ end
124
+ end
125
+
126
+ # Returns the list of global option definitionss.
127
+ def global_option_definitions
128
+ []
129
+ end
130
+
131
+ # Adds the given command to the list of commands. Adding a command will
132
+ # also cause the command's +base+ to be set to this instance.
133
+ def add_command(command)
134
+ @commands << command
135
+ command.base = self
136
+ end
137
+
138
+ # Handles the given optio
139
+ def handle_option(option)
140
+ false
141
+ end
142
+
143
+ private
144
+
145
+ # Returns true if the given string is an option (i.e. -foo or --foo),
146
+ # false otherwise.
147
+ def is_option?(string)
148
+ string =~ /^-/
149
+ end
150
+
151
+ end
152
+
153
+ end