summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/xmlrpc/client.rb
blob: e19275759505835bfe327f466a942c75a36748cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
require 'puppet/sslcertificates'
require 'puppet/network/http_pool'
require 'openssl'
require 'puppet/external/base64'

require 'xmlrpc/client'
require 'net/https'
require 'yaml'

module Puppet::Network
    class ClientError < Puppet::Error; end
    class XMLRPCClientError < Puppet::Error; end
    class XMLRPCClient < ::XMLRPC::Client

        attr_accessor :puppet_server, :puppet_port
        @clients = {}

        class << self
            include Puppet::Util
            include Puppet::Util::ClassGen
        end

        # Create a netclient for each handler
        def self.mkclient(handler)
            interface = handler.interface
            namespace = interface.prefix

            # Create a subclass for every client type.  This is
            # so that all of the methods are on their own class,
            # so that their namespaces can define the same methods if
            # they want.
            constant = handler.name.to_s.capitalize
            name = namespace.downcase
            newclient = genclass(name, :hash => @clients, :constant => constant)

            interface.methods.each { |ary|
                method = ary[0]
                newclient.send(:define_method,method) { |*args|
                    make_rpc_call(namespace, method, *args)
                }
            }

            newclient
        end

        def self.handler_class(handler)
            @clients[handler] || self.mkclient(handler)
        end

        class ErrorHandler
            def initialize(&block)
                singleton_class.define_method(:execute, &block)
            end
        end

        # Use a class variable so all subclasses have access to it.
        @@error_handlers = {}

        def self.error_handler(exception)
            if handler = @@error_handlers[exception.class]
                return handler
            else
                return @@error_handlers[:default]
            end
        end

        def self.handle_error(*exceptions, &block)
            handler = ErrorHandler.new(&block)

            exceptions.each do |exception|
                @@error_handlers[exception] = handler
            end
        end

        handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method|
            if detail.message =~ /bad write retry/
                Puppet.warning "Transient SSL write error; restarting connection and retrying"
                client.recycle_connection
                return :retry
            end
            ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str|
                Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str)
            end
            raise XMLRPCClientError, "Certificates were not trusted: #{detail}"
        end

        handle_error(:default) do |client, detail, namespace, method|
            if detail.message.to_s =~ /^Wrong size\. Was \d+, should be \d+$/
                Puppet.warning "XMLRPC returned wrong size.  Retrying."
                return :retry
            end
            Puppet.err "Could not call #{namespace}.#{method}: #{detail.inspect}"
            error = XMLRPCClientError.new(detail.to_s)
            error.set_backtrace detail.backtrace
            raise error
        end

        handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method|
            if detail.message =~ /bad write retry/
                Puppet.warning "Transient SSL write error; restarting connection and retrying"
                client.recycle_connection
                return :retry
            end
            ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str|
                Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str)
            end
            raise XMLRPCClientError, "Certificates were not trusted: #{detail}"
        end

        handle_error(::XMLRPC::FaultException) do |client, detail, namespace, method|
            raise XMLRPCClientError, detail.faultString
        end

        handle_error(Errno::ECONNREFUSED) do |client, detail, namespace, method|
            msg = "Could not connect to #{client.host} on port #{client.port}"
            raise XMLRPCClientError, msg
        end

        handle_error(SocketError) do |client, detail, namespace, method|
            Puppet.err "Could not find server #{@host}: #{detail}"
            error = XMLRPCClientError.new("Could not find server #{client.host}")
            error.set_backtrace detail.backtrace
            raise error
        end

        handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method|
            Puppet.info "Other end went away; restarting connection and retrying"
            client.recycle_connection
            return :retry
        end

        handle_error(Timeout::Error) do |client, detail, namespace, method|
            Puppet.err "Connection timeout calling #{namespace}.#{method}: #{detail}"
            error = XMLRPCClientError.new("Connection Timeout")
            error.set_backtrace(detail.backtrace)
            raise error
        end

        def make_rpc_call(namespace, method, *args)
            Puppet.debug "Calling #{namespace}.#{method}"
            begin
                call("#{namespace}.#{method}",*args)
            rescue SystemExit,NoMemoryError
                raise
            rescue Exception => detail
                retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry
            end
        ensure
            http.finish if http.started?
        end

        def http
            @http = Puppet::Network::HttpPool.http_instance(host, port, true) unless @http
            @http
        end

        attr_reader :host, :port

        def initialize(hash = {})
            hash[:Path] ||= "/RPC2"
            hash[:Server] ||= Puppet[:server]
            hash[:Port] ||= Puppet[:masterport]
            hash[:HTTPProxyHost] ||= Puppet[:http_proxy_host]
            hash[:HTTPProxyPort] ||= Puppet[:http_proxy_port]

            if "none" == hash[:HTTPProxyHost]
                hash[:HTTPProxyHost] = nil
                hash[:HTTPProxyPort] = nil
            end


                        super(
                
                hash[:Server],
                hash[:Path],
                hash[:Port],
                hash[:HTTPProxyHost],
                hash[:HTTPProxyPort],
        
                nil, # user
                nil, # password
                true, # use_ssl
                Puppet[:configtimeout] # use configured timeout (#1176)
            )
            @http = Puppet::Network::HttpPool.http_instance(@host, @port)
        end

        # Get rid of our existing connection, replacing it with a new one.
        # This should only happen if we lose our connection somehow (e.g., an EPIPE)
        # or we've just downloaded certs and we need to create new http instances
        # with the certs added.
        def recycle_connection
            http.finish if http.started?
            @http = nil
            self.http # force a new one
        end

        def start
            begin
                @http.start unless @http.started?
            rescue => detail
                Puppet.err "Could not connect to server: #{detail}"
            end
        end

        def local
            false
        end

        def local?
            false
        end
    end
end