vcap_common 1.0.10

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,211 @@
1
+ require 'posix-spawn'
2
+
3
+ module VCAP
4
+ class SubprocessError < StandardError; end
5
+
6
+ # Command exited with unexpected status code
7
+ class SubprocessStatusError < SubprocessError
8
+ attr_reader :command, :status, :stdout, :stderr
9
+
10
+ def initialize(command, stdout, stderr, status)
11
+ @command = command
12
+ @status = status
13
+ @stdout = stdout
14
+ @stderr = stderr
15
+ end
16
+
17
+ def to_s
18
+ "ERROR: Command '#{@command}' exited with status '#{status.exitstatus}'"
19
+ end
20
+ end
21
+
22
+ # Command ran longer than allowed
23
+ class SubprocessTimeoutError < SubprocessError
24
+ attr_reader :command, :timeout, :stdout, :stderr
25
+
26
+ def initialize(timeout, command, stdout, stderr)
27
+ @command = command
28
+ @timeout = timeout
29
+ @stdout = stdout
30
+ @stderr = stderr
31
+ end
32
+
33
+ def to_s
34
+ "ERROR: Command '#{@command}' timed out"
35
+ end
36
+ end
37
+
38
+ # Failure reading from stdin/stdout
39
+ class SubprocessReadError < SubprocessError
40
+ attr_reader :command, :stdout, :stderr
41
+
42
+ def initialize(failed_iostr, command, stdout, stderr)
43
+ @failed_iostr = failed_iostr
44
+ @command = command
45
+ @stdout = stdout
46
+ @stderr = stderr
47
+ end
48
+
49
+ def to_s
50
+ "ERROR: Failed reading from #{@failed_iostr} while executing '#{@command}'"
51
+ end
52
+ end
53
+
54
+ # Utility class providing:
55
+ # - Ability to capture stdout/stderr of a command
56
+ # - Exceptions when commands fail (useful for running a chain of commands)
57
+ # - Easier integration with unit tests.
58
+ class Subprocess
59
+ READ_SIZE = 4096
60
+
61
+ def self.run(*args)
62
+ VCAP::Subprocess.new.run(*args)
63
+ end
64
+
65
+ # Runs the supplied command in a subshell.
66
+ #
67
+ # @param command String The command to be run
68
+ # @param expected_exit_status Integer The expected exit status of the command in [0, 255]
69
+ # @param timeout Integer How long the command should be allowed to run for
70
+ # nil indicates no timeout
71
+ # @param options Hash Options to be passed to Posix::Spawn
72
+ # See https://github.com/rtomayko/posix-spawn
73
+ # @param env Hash Environment to be passed to Posix::Spawn
74
+ # See https://github.com/rtomayko/posix-spawn
75
+ #
76
+ # @raise VCAP::SubprocessStatusError Thrown if the exit status does not match the expected
77
+ # exit status.
78
+ # @raise VCAP::SubprocessTimeoutError Thrown if a timeout occurs.
79
+ # @raise VCAP::SubprocessReadError Thrown if there is an error reading from any of the pipes
80
+ # to the child.
81
+ #
82
+ # @return Array An array of [stdout, stderr, status]. Note that status
83
+ # is an instance of Process::Status.
84
+ #
85
+ def run(command, expected_exit_status=0, timeout=nil, options={}, env={})
86
+ # We use a pipe to ourself to time out long running commands (if desired) as follows:
87
+ # 1. Set up a pipe to ourselves
88
+ # 2. Install a signal handler that writes to one end of our pipe on SIGCHLD
89
+ # 3. Select on the read end of our pipe and check if our process exited
90
+ sigchld_r, sigchld_w = IO.pipe
91
+ prev_sigchld_handler = install_sigchld_handler(sigchld_w)
92
+
93
+ start = Time.now.to_i
94
+ child_pid, stdin, stdout, stderr = POSIX::Spawn.popen4(env, command, options)
95
+ stdin.close
96
+
97
+ # Used to look up the name of an io object when an errors occurs while
98
+ # reading from it, as well as to look up the corresponding buffer to
99
+ # append to.
100
+ io_map = {
101
+ stderr => { :name => 'stderr', :buf => '' },
102
+ stdout => { :name => 'stdout', :buf => '' },
103
+ sigchld_r => { :name => 'sigchld_r', :buf => '' },
104
+ sigchld_w => { :name => 'sigchld_w', :buf => '' },
105
+ }
106
+
107
+ status = nil
108
+ time_left = timeout
109
+ read_cands = [stdout, stderr, sigchld_r]
110
+ error_cands = read_cands.dup
111
+
112
+ begin
113
+ while read_cands.length > 0
114
+ active_ios = IO.select(read_cands, nil, error_cands, time_left)
115
+
116
+ # Check if timeout was hit
117
+ if timeout
118
+ time_left = timeout - (Time.now.to_i - start)
119
+ unless active_ios && (time_left > 0)
120
+ raise VCAP::SubprocessTimeoutError.new(timeout,
121
+ command,
122
+ io_map[stdout][:buf],
123
+ io_map[stderr][:buf])
124
+ end
125
+ end
126
+
127
+ # Read as much as we can from the readable ios before blocking
128
+ for io in active_ios[0]
129
+ begin
130
+ io_map[io][:buf] << io.read_nonblock(READ_SIZE)
131
+ rescue IO::WaitReadable
132
+ # Reading would block, so put ourselves back on the loop
133
+ rescue EOFError
134
+ # Pipe has no more data, remove it from the readable/error set
135
+ # NB: We cannot break from the loop here, as the other pipes may have data to be read
136
+ read_cands.delete(io)
137
+ error_cands.delete(io)
138
+ end
139
+
140
+ # Our signal handler notified us that >= 1 children have exited;
141
+ # check if our child has exited.
142
+ if (io == sigchld_r) && Process.waitpid(child_pid, Process::WNOHANG)
143
+ status = $?
144
+ read_cands.delete(sigchld_r)
145
+ error_cands.delete(sigchld_r)
146
+ end
147
+ end
148
+
149
+ # Error reading from one or more pipes.
150
+ unless active_ios[2].empty?
151
+ io_names = active_ios[2].map {|io| io_map[io][:name] }
152
+ raise SubprocessReadError.new(io_names.join(', '),
153
+ command,
154
+ io_map[stdout][:buf],
155
+ io_map[stderr][:buf])
156
+ end
157
+ end
158
+
159
+ rescue
160
+ # A timeout or an error occurred while reading from one or more pipes.
161
+ # Kill the process if we haven't reaped its exit status already.
162
+ kill_pid(child_pid) unless status
163
+ raise
164
+
165
+ ensure
166
+ # Make sure we reap the child's exit status, close our fds, and restore
167
+ # the previous SIGCHLD handler
168
+ unless status
169
+ Process.waitpid(child_pid)
170
+ status = $?
171
+ end
172
+ io_map.each_key {|io| io.close unless io.closed? }
173
+ trap('CLD') { prev_sigchld_handler.call } if prev_sigchld_handler
174
+ end
175
+
176
+ unless status.exitstatus == expected_exit_status
177
+ raise SubprocessStatusError.new(command,
178
+ io_map[stdout][:buf],
179
+ io_map[stderr][:buf],
180
+ status)
181
+ end
182
+
183
+ [io_map[stdout][:buf], io_map[stderr][:buf], status]
184
+ end
185
+
186
+ private
187
+
188
+ def install_sigchld_handler(write_pipe)
189
+ prev_handler = trap('CLD') do
190
+ begin
191
+ # Notify select loop that a child exited. We use a nonblocking write
192
+ # to avoid writing more than PIPE_BUF bytes before we have the chance
193
+ # to drain the pipe. Note that we only need to write a single byte
194
+ # to detect if our child has exited.
195
+ write_pipe.write_nonblock('x') unless write_pipe.closed?
196
+ rescue IO::WaitWritable
197
+ end
198
+ prev_handler.call if prev_handler
199
+ end
200
+ prev_handler
201
+ end
202
+
203
+ def kill_pid(pid)
204
+ begin
205
+ Process.kill('KILL', pid)
206
+ rescue Errno::ESRCH
207
+ end
208
+ end
209
+ end
210
+
211
+ end
@@ -0,0 +1,47 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__),'../../common/lib'))
2
+ $:.unshift(File.join(File.dirname(__FILE__)))
3
+
4
+ require 'logger'
5
+ require 'vcap/subprocess'
6
+
7
+ module VCAP
8
+ module UserOps
9
+ class << self
10
+ def run(cmd, expected_exit_status = 0)
11
+ result = VCAP::Subprocess.run(cmd, expected_exit_status)
12
+ end
13
+
14
+ def group_exists?(name)
15
+ found = false
16
+ Etc.group { |g| found = true if g.name == name}
17
+ Etc.endgrent
18
+ found
19
+ end
20
+
21
+ def install_group(group_name)
22
+ run("addgroup --system #{group_name}")
23
+ end
24
+
25
+ def remove_group(group_name)
26
+ run("delgroup --system #{group_name}")
27
+ end
28
+
29
+ def user_exists?(name)
30
+ found = false
31
+ Etc.passwd { |u| found = true if u.name == name}
32
+ Etc.endpwent
33
+ found
34
+ end
35
+
36
+ def install_user(user_name, group_name)
37
+ run("adduser --system --quiet --no-create-home --home '/nonexistent' #{user_name}")
38
+ run("usermod -g #{group_name} #{user_name}")
39
+ end
40
+
41
+ def remove_user(user_name)
42
+ run("deluser #{user_name}")
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__),'..'))
2
+ $:.unshift(File.dirname(__FILE__))
3
+ require 'user_pool_util'
4
+ require 'user_ops'
5
+ require 'subprocess'
6
+
7
+ module VCAP
8
+ class UserPool
9
+ attr_accessor :free_users
10
+ attr_accessor :busy_users
11
+
12
+ def initialize(name, logger = nil)
13
+ @logger = logger || Logger.new(STDOUT)
14
+ UserPoolUtil.init
15
+ @free_users = UserPoolUtil.open_pool(name)
16
+ @busy_users = Hash.new
17
+ @logger.debug("Initialized user pool #{name} with #{@free_users.size} users.")
18
+ end
19
+
20
+ def alloc_user
21
+ user_name, user = @free_users.shift
22
+ if user_name != nil
23
+ @busy_users[user_name] = user
24
+ else
25
+ raise "out of users!!"
26
+ end
27
+ @logger.debug "alloc()'d user #{user_name}"
28
+ user
29
+ end
30
+
31
+ def free_user(user)
32
+ user_name = user[:user_name]
33
+ if @busy_users.has_key?(user_name)
34
+ VCAP::Subprocess.run("pkill -9 -u #{user_name}", 1)
35
+ @busy_users.delete(user_name)
36
+ @free_users[user_name] = user
37
+ @logger.debug "free()'d user #{user_name}"
38
+ else
39
+ raise "invalid free user: #{user_name}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
@@ -0,0 +1,107 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'logger'
4
+ require 'vcap/subprocess'
5
+ require 'user_ops'
6
+
7
+ module VCAP
8
+ module UserPoolUtil
9
+ class << self
10
+ def init(logger = nil)
11
+ @logger = logger || Logger.new(STDOUT)
12
+ end
13
+
14
+ def user_from_num(name, num)
15
+ "user-pool-#{name}-#{num}"
16
+ end
17
+
18
+ def group_from_name(name)
19
+ "user-pool-#{name}"
20
+ end
21
+
22
+ def kill_group_procs(group_name)
23
+ @logger.debug("killing all procs in group #{group_name}")
24
+ #XXX -- fixme VCAP::Subprocess.run("pkill -9 -G #{group_name}" , 0)
25
+ end
26
+
27
+ def install_pool(name, size)
28
+ raise ArgumentError("pool name must not contain dashes") if name =~ /-/
29
+ group_name = group_from_name(name)
30
+
31
+ @logger.info("Creating user pool #{name} with #{size} users.")
32
+ if VCAP::UserOps.group_exists?(group_name)
33
+ raise ArgumentError.new("group #{group_name} already exists")
34
+ end
35
+ VCAP::UserOps.install_group(group_name)
36
+ kill_group_procs(group_name)
37
+
38
+ begin
39
+ 1.upto(size) do |number|
40
+ user_name = user_from_num(name, number)
41
+ if VCAP::UserOps.user_exists?(user_name)
42
+ VCAP::UserOps.remove_user(user_name)
43
+ @logger.warn("User reset occured for user #{user_name}!")
44
+ end
45
+ @logger.debug("installing user #{user_name}")
46
+ VCAP::UserOps.install_user(user_name, group_name)
47
+ end
48
+ rescue => e
49
+ @logger.error e.to_s
50
+ @logger.error("pool creation failed, cleaning up")
51
+ remove_pool(name)
52
+ end
53
+ end
54
+
55
+ def remove_pool(name)
56
+ @logger.info("Removing user pool #{name}.")
57
+ group_name = group_from_name(name)
58
+ kill_group_procs(group_name)
59
+
60
+ Etc.passwd { |u|
61
+ if u.name.split('-')[2] == name
62
+ @logger.debug "removed user #{u.name}"
63
+ VCAP::UserOps.remove_user(u.name)
64
+ end
65
+ }
66
+ Etc.endpwent
67
+
68
+ if VCAP::UserOps.group_exists?(group_name)
69
+ VCAP::UserOps.remove_group(group_name)
70
+ else
71
+ @logger.warn "Pool group #{group_name} missing!!"
72
+ end
73
+ end
74
+
75
+ def pool_exists?(name)
76
+ group_name = group_from_name(name)
77
+ VCAP::UserOps.group_exists?(group_name)
78
+ end
79
+
80
+ def open_pool(name)
81
+ group_name = group_from_name(name)
82
+ pool_users = Hash.new
83
+ unless VCAP::UserOps.group_exists?(group_name)
84
+ raise ArgumentError.new("no group named #{group_name} exists - can't open pool.")
85
+ end
86
+ Etc.passwd { |u|
87
+ if u.name.split('-')[2] == name
88
+ pool_users[u.name] = {:user_name => u.name, :uid => u.uid, :gid => u.gid}
89
+ end
90
+ }
91
+ pool_users
92
+ end
93
+
94
+ def pool_list
95
+ list = []
96
+ Etc.group { |g|
97
+ if ['user','pool'] == g.name.split('-')[0..1]
98
+ list.push(g.name.split('-')[2])
99
+ end
100
+ }
101
+ Etc.endgrent
102
+ list.map {|name| "#{name} #{open_pool(name).size}"}
103
+ end
104
+
105
+ end
106
+ end
107
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vcap_common
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.10
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Derek Collison
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-02-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.12.11.cloudfoundry.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.12.11.cloudfoundry.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: thin
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.3.1
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.3.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: yajl-ruby
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.8.3
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.3
62
+ - !ruby/object:Gem::Dependency
63
+ name: nats
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.4.22.beta.8
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.4.22.beta.8
78
+ - !ruby/object:Gem::Dependency
79
+ name: posix-spawn
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.3.6
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.3.6
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.9.2
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.9.2
110
+ description: common vcap classes/methods
111
+ email:
112
+ - derek.collison@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - lib/json_message.rb
118
+ - lib/json_schema.rb
119
+ - lib/services/api/async_requests.rb
120
+ - lib/services/api/clients/service_gateway_client.rb
121
+ - lib/services/api/const.rb
122
+ - lib/services/api/messages.rb
123
+ - lib/services/api/util.rb
124
+ - lib/services/api.rb
125
+ - lib/vcap/common.rb
126
+ - lib/vcap/component.rb
127
+ - lib/vcap/config.rb
128
+ - lib/vcap/fiber_tracing.rb
129
+ - lib/vcap/json_schema.rb
130
+ - lib/vcap/priority_queue.rb
131
+ - lib/vcap/process_utils.rb
132
+ - lib/vcap/quota.rb
133
+ - lib/vcap/rolling_metric.rb
134
+ - lib/vcap/spec/em.rb
135
+ - lib/vcap/spec/forked_component/base.rb
136
+ - lib/vcap/spec/forked_component/nats_server.rb
137
+ - lib/vcap/spec/forked_component.rb
138
+ - lib/vcap/subprocess.rb
139
+ - lib/vcap/user_pools/user_ops.rb
140
+ - lib/vcap/user_pools/user_pool.rb
141
+ - lib/vcap/user_pools/user_pool_util.rb
142
+ homepage: http://github.com/vmware-ac/core
143
+ licenses: []
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 1.8.22
163
+ signing_key:
164
+ specification_version: 3
165
+ summary: vcap common
166
+ test_files: []