shu-san-scripts 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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