shu-san-scripts 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,104 @@
1
+ module Cri
2
+
3
+ # Cri::Command represents a command that can be executed on the commandline.
4
+ # It is an abstract superclass for all commands.
5
+ class Command
6
+
7
+ attr_accessor :base
8
+
9
+ # Returns a string containing the name of thi command. Subclasses must
10
+ # implement this method.
11
+ def name
12
+ raise NotImplementedError.new("Command subclasses should override #name")
13
+ end
14
+
15
+ # Returns an array of strings containing the aliases for this command.
16
+ # Subclasses must implement this method.
17
+ def aliases
18
+ raise NotImplementedError.new("Command subclasses should override #aliases")
19
+ end
20
+
21
+ # Returns a string containing this command's short description, which
22
+ # should not be longer than 50 characters. Subclasses must implement this
23
+ # method.
24
+ def short_desc
25
+ raise NotImplementedError.new("Command subclasses should override #short_desc")
26
+ end
27
+
28
+ # Returns a string containing this command's complete description, which
29
+ # should explain what this command does and how it works in detail.
30
+ # Subclasses must implement this method.
31
+ def long_desc
32
+ raise NotImplementedError.new("Command subclasses should override #long_desc")
33
+ end
34
+
35
+ # Returns a string containing this command's usage. Subclasses must
36
+ # implement this method.
37
+ def usage
38
+ raise NotImplementedError.new("Command subclasses should override #usage")
39
+ end
40
+
41
+ # Returns an array containing this command's option definitions. See the
42
+ # documentation for Cri::OptionParser for details on what option
43
+ # definitions look like. Subclasses may implement this method if the
44
+ # command has options.
45
+ def option_definitions
46
+ []
47
+ end
48
+
49
+ # Executes the command. Subclasses must implement this method
50
+ # (obviously... what's the point of a command that can't be run?).
51
+ #
52
+ # +options+:: A hash containing the parsed commandline options. For
53
+ # example, '--foo=bar' will be converted into { :foo => 'bar'
54
+ # }. See the Cri::OptionParser documentation for details.
55
+ #
56
+ # +arguments+:: An array of strings representing the commandline arguments
57
+ # given to this command.
58
+ def run(options, arguments)
59
+ raise NotImplementedError.new("Command subclasses should override #run")
60
+ end
61
+
62
+ # Returns the help text for this command.
63
+ def help
64
+ text = ''
65
+
66
+ # Append usage
67
+ text << usage + "\n"
68
+
69
+ # Append aliases
70
+ unless aliases.empty?
71
+ text << "\n"
72
+ text << "aliases: #{aliases.join(' ')}\n"
73
+ end
74
+
75
+ # Append short description
76
+ text << "\n"
77
+ text << short_desc + "\n"
78
+
79
+ # Append long description
80
+ text << "\n"
81
+ text << long_desc.wrap_and_indent(78, 4) + "\n"
82
+
83
+ # Append options
84
+ unless option_definitions.empty?
85
+ text << "\n"
86
+ text << "options:\n"
87
+ text << "\n"
88
+ option_definitions.sort { |x,y| x[:long] <=> y[:long] }.each do |opt_def|
89
+ text << sprintf(" -%1s --%-10s %s\n\n", opt_def[:short], opt_def[:long], opt_def[:desc].wrap_and_indent(78, 20).lstrip)
90
+ end
91
+ end
92
+
93
+ # Return text
94
+ text
95
+ end
96
+
97
+ # Compares this command's name to the other given command's name.
98
+ def <=>(other)
99
+ self.name <=> other.name
100
+ end
101
+
102
+ end
103
+
104
+ end