summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/xmlrpc/client.rb
blob: 37ace2101b6ee8d82711973f1d2d708eb149e426 (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
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|
                    Puppet.debug "Calling %s.%s" % [namespace, method]
                    begin
                        call("%s.%s" % [namespace, method.to_s],*args)
                    rescue OpenSSL::SSL::SSLError => detail
                        if detail.message =~ /bad write retry/
                            Puppet.warning "Transient SSL write error; restarting connection and retrying"
                            self.recycle_connection
                            retry
                        end
                        ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str|
                            if detail.message.include?(str)
                                Puppet.warning "Certificate validation failed; consider using the certname configuration option"
                            end
                        end 
                        raise XMLRPCClientError,
                            "Certificates were not trusted: %s" % detail
                    rescue ::XMLRPC::FaultException => detail
                        raise XMLRPCClientError, detail.faultString
                    rescue Errno::ECONNREFUSED => detail
                        msg = "Could not connect to %s on port %s" %
                            [@host, @port]
                        raise XMLRPCClientError, msg
                    rescue SocketError => detail
                        Puppet.err "Could not find server %s: %s" %
                            [@host, detail.to_s]
                        error = XMLRPCClientError.new(
                            "Could not find server %s" % @host
                        )
                        error.set_backtrace detail.backtrace
                        raise error
                    rescue Errno::EPIPE, EOFError
                        Puppet.warning "Other end went away; restarting connection and retrying"
                        self.recycle_connection
                        retry
                    rescue Timeout::Error => detail
                        Puppet.err "Connection timeout calling %s.%s: %s" %
                            [namespace, method, detail.to_s]
                        error = XMLRPCClientError.new("Connection Timeout")
                        error.set_backtrace(detail.backtrace)
                        raise error
                    rescue => detail
                        if detail.message =~ /^Wrong size\. Was \d+, should be \d+$/
                            Puppet.warning "XMLRPC returned wrong size.  Retrying."
                            retry
                        end    
                        Puppet.err "Could not call %s.%s: %s" %
                            [namespace, method, detail.inspect]
                        error = XMLRPCClientError.new(detail.to_s)
                        error.set_backtrace detail.backtrace
                        raise error
                    end
                }
            }

            return newclient
        end

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

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

        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 = Puppet::Network::HttpPool.http_instance(@host, @port, true) # reset the instance
        end
        
        def start
            begin
                @http.start unless @http.started?
            rescue => detail
                Puppet.err "Could not connect to server: %s" % detail
            end
        end

        def local
            false
        end

        def local?
            false
        end
    end
end