Statistics
| Branch: | Tag: | Revision:

root / lib / rpc.py @ d5ea30e8

History | View | Annotate | Download (20.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Inter-node RPC library.
23

24
"""
25

    
26
# pylint: disable=C0103,R0201,R0904
27
# C0103: Invalid name, since call_ are not valid
28
# R0201: Method could be a function, we keep all rpcs instance methods
29
# as not to change them back and forth between static/instance methods
30
# if they need to start using instance attributes
31
# R0904: Too many public methods
32

    
33
import os
34
import logging
35
import zlib
36
import base64
37
import pycurl
38
import threading
39

    
40
from ganeti import utils
41
from ganeti import objects
42
from ganeti import http
43
from ganeti import serializer
44
from ganeti import constants
45
from ganeti import errors
46
from ganeti import netutils
47
from ganeti import ssconf
48
from ganeti import runtime
49
from ganeti import compat
50
from ganeti import rpc_defs
51

    
52
# Special module generated at build time
53
from ganeti import _generated_rpc
54

    
55
# pylint has a bug here, doesn't see this import
56
import ganeti.http.client  # pylint: disable=W0611
57

    
58

    
59
# Timeout for connecting to nodes (seconds)
60
_RPC_CONNECT_TIMEOUT = 5
61

    
62
_RPC_CLIENT_HEADERS = [
63
  "Content-type: %s" % http.HTTP_APP_JSON,
64
  "Expect:",
65
  ]
66

    
67
# Various time constants for the timeout table
68
_TMO_URGENT = 60 # one minute
69
_TMO_FAST = 5 * 60 # five minutes
70
_TMO_NORMAL = 15 * 60 # 15 minutes
71
_TMO_SLOW = 3600 # one hour
72
_TMO_4HRS = 4 * 3600
73
_TMO_1DAY = 86400
74

    
75
#: Special value to describe an offline host
76
_OFFLINE = object()
77

    
78

    
79
def Init():
80
  """Initializes the module-global HTTP client manager.
81

82
  Must be called before using any RPC function and while exactly one thread is
83
  running.
84

85
  """
86
  # curl_global_init(3) and curl_global_cleanup(3) must be called with only
87
  # one thread running. This check is just a safety measure -- it doesn't
88
  # cover all cases.
89
  assert threading.activeCount() == 1, \
90
         "Found more than one active thread when initializing pycURL"
91

    
92
  logging.info("Using PycURL %s", pycurl.version)
93

    
94
  pycurl.global_init(pycurl.GLOBAL_ALL)
95

    
96

    
97
def Shutdown():
98
  """Stops the module-global HTTP client manager.
99

100
  Must be called before quitting the program and while exactly one thread is
101
  running.
102

103
  """
104
  pycurl.global_cleanup()
105

    
106

    
107
def _ConfigRpcCurl(curl):
108
  noded_cert = str(constants.NODED_CERT_FILE)
109

    
110
  curl.setopt(pycurl.FOLLOWLOCATION, False)
111
  curl.setopt(pycurl.CAINFO, noded_cert)
112
  curl.setopt(pycurl.SSL_VERIFYHOST, 0)
113
  curl.setopt(pycurl.SSL_VERIFYPEER, True)
114
  curl.setopt(pycurl.SSLCERTTYPE, "PEM")
115
  curl.setopt(pycurl.SSLCERT, noded_cert)
116
  curl.setopt(pycurl.SSLKEYTYPE, "PEM")
117
  curl.setopt(pycurl.SSLKEY, noded_cert)
118
  curl.setopt(pycurl.CONNECTTIMEOUT, _RPC_CONNECT_TIMEOUT)
119

    
120

    
121
def RunWithRPC(fn):
122
  """RPC-wrapper decorator.
123

124
  When applied to a function, it runs it with the RPC system
125
  initialized, and it shutsdown the system afterwards. This means the
126
  function must be called without RPC being initialized.
127

128
  """
129
  def wrapper(*args, **kwargs):
130
    Init()
131
    try:
132
      return fn(*args, **kwargs)
133
    finally:
134
      Shutdown()
135
  return wrapper
136

    
137

    
138
def _Compress(data):
139
  """Compresses a string for transport over RPC.
140

141
  Small amounts of data are not compressed.
142

143
  @type data: str
144
  @param data: Data
145
  @rtype: tuple
146
  @return: Encoded data to send
147

148
  """
149
  # Small amounts of data are not compressed
150
  if len(data) < 512:
151
    return (constants.RPC_ENCODING_NONE, data)
152

    
153
  # Compress with zlib and encode in base64
154
  return (constants.RPC_ENCODING_ZLIB_BASE64,
155
          base64.b64encode(zlib.compress(data, 3)))
156

    
157

    
158
class RpcResult(object):
159
  """RPC Result class.
160

161
  This class holds an RPC result. It is needed since in multi-node
162
  calls we can't raise an exception just because one one out of many
163
  failed, and therefore we use this class to encapsulate the result.
164

165
  @ivar data: the data payload, for successful results, or None
166
  @ivar call: the name of the RPC call
167
  @ivar node: the name of the node to which we made the call
168
  @ivar offline: whether the operation failed because the node was
169
      offline, as opposed to actual failure; offline=True will always
170
      imply failed=True, in order to allow simpler checking if
171
      the user doesn't care about the exact failure mode
172
  @ivar fail_msg: the error message if the call failed
173

174
  """
175
  def __init__(self, data=None, failed=False, offline=False,
176
               call=None, node=None):
177
    self.offline = offline
178
    self.call = call
179
    self.node = node
180

    
181
    if offline:
182
      self.fail_msg = "Node is marked offline"
183
      self.data = self.payload = None
184
    elif failed:
185
      self.fail_msg = self._EnsureErr(data)
186
      self.data = self.payload = None
187
    else:
188
      self.data = data
189
      if not isinstance(self.data, (tuple, list)):
190
        self.fail_msg = ("RPC layer error: invalid result type (%s)" %
191
                         type(self.data))
192
        self.payload = None
193
      elif len(data) != 2:
194
        self.fail_msg = ("RPC layer error: invalid result length (%d), "
195
                         "expected 2" % len(self.data))
196
        self.payload = None
197
      elif not self.data[0]:
198
        self.fail_msg = self._EnsureErr(self.data[1])
199
        self.payload = None
200
      else:
201
        # finally success
202
        self.fail_msg = None
203
        self.payload = data[1]
204

    
205
    for attr_name in ["call", "data", "fail_msg",
206
                      "node", "offline", "payload"]:
207
      assert hasattr(self, attr_name), "Missing attribute %s" % attr_name
208

    
209
  @staticmethod
210
  def _EnsureErr(val):
211
    """Helper to ensure we return a 'True' value for error."""
212
    if val:
213
      return val
214
    else:
215
      return "No error information"
216

    
217
  def Raise(self, msg, prereq=False, ecode=None):
218
    """If the result has failed, raise an OpExecError.
219

220
    This is used so that LU code doesn't have to check for each
221
    result, but instead can call this function.
222

223
    """
224
    if not self.fail_msg:
225
      return
226

    
227
    if not msg: # one could pass None for default message
228
      msg = ("Call '%s' to node '%s' has failed: %s" %
229
             (self.call, self.node, self.fail_msg))
230
    else:
231
      msg = "%s: %s" % (msg, self.fail_msg)
232
    if prereq:
233
      ec = errors.OpPrereqError
234
    else:
235
      ec = errors.OpExecError
236
    if ecode is not None:
237
      args = (msg, ecode)
238
    else:
239
      args = (msg, )
240
    raise ec(*args) # pylint: disable=W0142
241

    
242

    
243
def _SsconfResolver(node_list, _,
244
                    ssc=ssconf.SimpleStore,
245
                    nslookup_fn=netutils.Hostname.GetIP):
246
  """Return addresses for given node names.
247

248
  @type node_list: list
249
  @param node_list: List of node names
250
  @type ssc: class
251
  @param ssc: SimpleStore class that is used to obtain node->ip mappings
252
  @type nslookup_fn: callable
253
  @param nslookup_fn: function use to do NS lookup
254
  @rtype: list of tuple; (string, string)
255
  @return: List of tuples containing node name and IP address
256

257
  """
258
  ss = ssc()
259
  iplist = ss.GetNodePrimaryIPList()
260
  family = ss.GetPrimaryIPFamily()
261
  ipmap = dict(entry.split() for entry in iplist)
262

    
263
  result = []
264
  for node in node_list:
265
    ip = ipmap.get(node)
266
    if ip is None:
267
      ip = nslookup_fn(node, family=family)
268
    result.append((node, ip))
269

    
270
  return result
271

    
272

    
273
class _StaticResolver:
274
  def __init__(self, addresses):
275
    """Initializes this class.
276

277
    """
278
    self._addresses = addresses
279

    
280
  def __call__(self, hosts, _):
281
    """Returns static addresses for hosts.
282

283
    """
284
    assert len(hosts) == len(self._addresses)
285
    return zip(hosts, self._addresses)
286

    
287

    
288
def _CheckConfigNode(name, node, accept_offline_node):
289
  """Checks if a node is online.
290

291
  @type name: string
292
  @param name: Node name
293
  @type node: L{objects.Node} or None
294
  @param node: Node object
295

296
  """
297
  if node is None:
298
    # Depend on DNS for name resolution
299
    ip = name
300
  elif node.offline and not accept_offline_node:
301
    ip = _OFFLINE
302
  else:
303
    ip = node.primary_ip
304
  return (name, ip)
305

    
306

    
307
def _NodeConfigResolver(single_node_fn, all_nodes_fn, hosts, opts):
308
  """Calculate node addresses using configuration.
309

310
  """
311
  accept_offline_node = (opts is rpc_defs.ACCEPT_OFFLINE_NODE)
312

    
313
  assert accept_offline_node or opts is None, "Unknown option"
314

    
315
  # Special case for single-host lookups
316
  if len(hosts) == 1:
317
    (name, ) = hosts
318
    return [_CheckConfigNode(name, single_node_fn(name), accept_offline_node)]
319
  else:
320
    all_nodes = all_nodes_fn()
321
    return [_CheckConfigNode(name, all_nodes.get(name, None),
322
                             accept_offline_node)
323
            for name in hosts]
324

    
325

    
326
class _RpcProcessor:
327
  def __init__(self, resolver, port, lock_monitor_cb=None):
328
    """Initializes this class.
329

330
    @param resolver: callable accepting a list of hostnames, returning a list
331
      of tuples containing name and IP address (IP address can be the name or
332
      the special value L{_OFFLINE} to mark offline machines)
333
    @type port: int
334
    @param port: TCP port
335
    @param lock_monitor_cb: Callable for registering with lock monitor
336

337
    """
338
    self._resolver = resolver
339
    self._port = port
340
    self._lock_monitor_cb = lock_monitor_cb
341

    
342
  @staticmethod
343
  def _PrepareRequests(hosts, port, procedure, body, read_timeout):
344
    """Prepares requests by sorting offline hosts into separate list.
345

346
    @type body: dict
347
    @param body: a dictionary with per-host body data
348

349
    """
350
    results = {}
351
    requests = {}
352

    
353
    assert isinstance(body, dict)
354
    assert len(body) == len(hosts)
355
    assert compat.all(isinstance(v, str) for v in body.values())
356
    assert frozenset(map(compat.fst, hosts)) == frozenset(body.keys()), \
357
        "%s != %s" % (hosts, body.keys())
358

    
359
    for (name, ip) in hosts:
360
      if ip is _OFFLINE:
361
        # Node is marked as offline
362
        results[name] = RpcResult(node=name, offline=True, call=procedure)
363
      else:
364
        requests[name] = \
365
          http.client.HttpClientRequest(str(ip), port,
366
                                        http.HTTP_PUT, str("/%s" % procedure),
367
                                        headers=_RPC_CLIENT_HEADERS,
368
                                        post_data=body[name],
369
                                        read_timeout=read_timeout,
370
                                        nicename="%s/%s" % (name, procedure),
371
                                        curl_config_fn=_ConfigRpcCurl)
372

    
373
    return (results, requests)
374

    
375
  @staticmethod
376
  def _CombineResults(results, requests, procedure):
377
    """Combines pre-computed results for offline hosts with actual call results.
378

379
    """
380
    for name, req in requests.items():
381
      if req.success and req.resp_status_code == http.HTTP_OK:
382
        host_result = RpcResult(data=serializer.LoadJson(req.resp_body),
383
                                node=name, call=procedure)
384
      else:
385
        # TODO: Better error reporting
386
        if req.error:
387
          msg = req.error
388
        else:
389
          msg = req.resp_body
390

    
391
        logging.error("RPC error in %s on node %s: %s", procedure, name, msg)
392
        host_result = RpcResult(data=msg, failed=True, node=name,
393
                                call=procedure)
394

    
395
      results[name] = host_result
396

    
397
    return results
398

    
399
  def __call__(self, hosts, procedure, body, read_timeout, resolver_opts,
400
               _req_process_fn=None):
401
    """Makes an RPC request to a number of nodes.
402

403
    @type hosts: sequence
404
    @param hosts: Hostnames
405
    @type procedure: string
406
    @param procedure: Request path
407
    @type body: dictionary
408
    @param body: dictionary with request bodies per host
409
    @type read_timeout: int or None
410
    @param read_timeout: Read timeout for request
411

412
    """
413
    assert read_timeout is not None, \
414
      "Missing RPC read timeout for procedure '%s'" % procedure
415

    
416
    if _req_process_fn is None:
417
      _req_process_fn = http.client.ProcessRequests
418

    
419
    (results, requests) = \
420
      self._PrepareRequests(self._resolver(hosts, resolver_opts), self._port,
421
                            procedure, body, read_timeout)
422

    
423
    _req_process_fn(requests.values(), lock_monitor_cb=self._lock_monitor_cb)
424

    
425
    assert not frozenset(results).intersection(requests)
426

    
427
    return self._CombineResults(results, requests, procedure)
428

    
429

    
430
class _RpcClientBase:
431
  def __init__(self, resolver, encoder_fn, lock_monitor_cb=None,
432
               _req_process_fn=None):
433
    """Initializes this class.
434

435
    """
436
    proc = _RpcProcessor(resolver,
437
                         netutils.GetDaemonPort(constants.NODED),
438
                         lock_monitor_cb=lock_monitor_cb)
439
    self._proc = compat.partial(proc, _req_process_fn=_req_process_fn)
440
    self._encoder = compat.partial(self._EncodeArg, encoder_fn)
441

    
442
  @staticmethod
443
  def _EncodeArg(encoder_fn, (argkind, value)):
444
    """Encode argument.
445

446
    """
447
    if argkind is None:
448
      return value
449
    else:
450
      return encoder_fn(argkind)(value)
451

    
452
  def _Call(self, cdef, node_list, args):
453
    """Entry point for automatically generated RPC wrappers.
454

455
    """
456
    (procedure, _, resolver_opts, timeout, argdefs,
457
     prep_fn, postproc_fn, _) = cdef
458

    
459
    if callable(timeout):
460
      read_timeout = timeout(args)
461
    else:
462
      read_timeout = timeout
463

    
464
    if callable(resolver_opts):
465
      req_resolver_opts = resolver_opts(args)
466
    else:
467
      req_resolver_opts = resolver_opts
468

    
469
    if len(args) != len(argdefs):
470
      raise errors.ProgrammerError("Number of passed arguments doesn't match")
471

    
472
    enc_args = map(self._encoder, zip(map(compat.snd, argdefs), args))
473
    if prep_fn is None:
474
      # for a no-op prep_fn, we serialise the body once, and then we
475
      # reuse it in the dictionary values
476
      body = serializer.DumpJson(enc_args)
477
      pnbody = dict((n, body) for n in node_list)
478
    else:
479
      # for a custom prep_fn, we pass the encoded arguments and the
480
      # node name to the prep_fn, and we serialise its return value
481
      assert callable(prep_fn)
482
      pnbody = dict((n, serializer.DumpJson(prep_fn(n, enc_args)))
483
                    for n in node_list)
484

    
485
    result = self._proc(node_list, procedure, pnbody, read_timeout,
486
                        req_resolver_opts)
487

    
488
    if postproc_fn:
489
      return dict(map(lambda (key, value): (key, postproc_fn(value)),
490
                      result.items()))
491
    else:
492
      return result
493

    
494

    
495
def _ObjectToDict(value):
496
  """Converts an object to a dictionary.
497

498
  @note: See L{objects}.
499

500
  """
501
  return value.ToDict()
502

    
503

    
504
def _ObjectListToDict(value):
505
  """Converts a list of L{objects} to dictionaries.
506

507
  """
508
  return map(_ObjectToDict, value)
509

    
510

    
511
def _EncodeNodeToDiskDict(value):
512
  """Encodes a dictionary with node name as key and disk objects as values.
513

514
  """
515
  return dict((name, _ObjectListToDict(disks))
516
              for name, disks in value.items())
517

    
518

    
519
def _PrepareFileUpload(filename):
520
  """Loads a file and prepares it for an upload to nodes.
521

522
  """
523
  data = _Compress(utils.ReadFile(filename))
524
  st = os.stat(filename)
525
  getents = runtime.GetEnts()
526
  return [filename, data, st.st_mode, getents.LookupUid(st.st_uid),
527
          getents.LookupGid(st.st_gid), st.st_atime, st.st_mtime]
528

    
529

    
530
def _PrepareFinalizeExportDisks(snap_disks):
531
  """Encodes disks for finalizing export.
532

533
  """
534
  flat_disks = []
535

    
536
  for disk in snap_disks:
537
    if isinstance(disk, bool):
538
      flat_disks.append(disk)
539
    else:
540
      flat_disks.append(disk.ToDict())
541

    
542
  return flat_disks
543

    
544

    
545
def _EncodeImportExportIO((ieio, ieioargs)):
546
  """Encodes import/export I/O information.
547

548
  """
549
  if ieio == constants.IEIO_RAW_DISK:
550
    assert len(ieioargs) == 1
551
    return (ieio, (ieioargs[0].ToDict(), ))
552

    
553
  if ieio == constants.IEIO_SCRIPT:
554
    assert len(ieioargs) == 2
555
    return (ieio, (ieioargs[0].ToDict(), ieioargs[1]))
556

    
557
  return (ieio, ieioargs)
558

    
559

    
560
def _EncodeBlockdevRename(value):
561
  """Encodes information for renaming block devices.
562

563
  """
564
  return [(d.ToDict(), uid) for d, uid in value]
565

    
566

    
567
#: Generic encoders
568
_ENCODERS = {
569
  rpc_defs.ED_OBJECT_DICT: _ObjectToDict,
570
  rpc_defs.ED_OBJECT_DICT_LIST: _ObjectListToDict,
571
  rpc_defs.ED_NODE_TO_DISK_DICT: _EncodeNodeToDiskDict,
572
  rpc_defs.ED_FILE_DETAILS: _PrepareFileUpload,
573
  rpc_defs.ED_COMPRESS: _Compress,
574
  rpc_defs.ED_FINALIZE_EXPORT_DISKS: _PrepareFinalizeExportDisks,
575
  rpc_defs.ED_IMPEXP_IO: _EncodeImportExportIO,
576
  rpc_defs.ED_BLOCKDEV_RENAME: _EncodeBlockdevRename,
577
  }
578

    
579

    
580
class RpcRunner(_RpcClientBase,
581
                _generated_rpc.RpcClientDefault,
582
                _generated_rpc.RpcClientBootstrap,
583
                _generated_rpc.RpcClientConfig):
584
  """RPC runner class.
585

586
  """
587
  def __init__(self, cfg, lock_monitor_cb, _req_process_fn=None, _getents=None):
588
    """Initialized the RPC runner.
589

590
    @type cfg: L{config.ConfigWriter}
591
    @param cfg: Configuration
592
    @type lock_monitor_cb: callable
593
    @param lock_monitor_cb: Lock monitor callback
594

595
    """
596
    self._cfg = cfg
597

    
598
    encoders = _ENCODERS.copy()
599

    
600
    # Add encoders requiring configuration object
601
    encoders.update({
602
      rpc_defs.ED_INST_DICT: self._InstDict,
603
      rpc_defs.ED_INST_DICT_HVP_BEP: self._InstDictHvpBep,
604
      rpc_defs.ED_INST_DICT_OSP: self._InstDictOsp,
605
      })
606

    
607
    # Resolver using configuration
608
    resolver = compat.partial(_NodeConfigResolver, cfg.GetNodeInfo,
609
                              cfg.GetAllNodesInfo)
610

    
611
    # Pylint doesn't recognize multiple inheritance properly, see
612
    # <http://www.logilab.org/ticket/36586> and
613
    # <http://www.logilab.org/ticket/35642>
614
    # pylint: disable=W0233
615
    _RpcClientBase.__init__(self, resolver, encoders.get,
616
                            lock_monitor_cb=lock_monitor_cb,
617
                            _req_process_fn=_req_process_fn)
618
    _generated_rpc.RpcClientConfig.__init__(self)
619
    _generated_rpc.RpcClientBootstrap.__init__(self)
620
    _generated_rpc.RpcClientDefault.__init__(self)
621

    
622
  def _InstDict(self, instance, hvp=None, bep=None, osp=None):
623
    """Convert the given instance to a dict.
624

625
    This is done via the instance's ToDict() method and additionally
626
    we fill the hvparams with the cluster defaults.
627

628
    @type instance: L{objects.Instance}
629
    @param instance: an Instance object
630
    @type hvp: dict or None
631
    @param hvp: a dictionary with overridden hypervisor parameters
632
    @type bep: dict or None
633
    @param bep: a dictionary with overridden backend parameters
634
    @type osp: dict or None
635
    @param osp: a dictionary with overridden os parameters
636
    @rtype: dict
637
    @return: the instance dict, with the hvparams filled with the
638
        cluster defaults
639

640
    """
641
    idict = instance.ToDict()
642
    cluster = self._cfg.GetClusterInfo()
643
    idict["hvparams"] = cluster.FillHV(instance)
644
    if hvp is not None:
645
      idict["hvparams"].update(hvp)
646
    idict["beparams"] = cluster.FillBE(instance)
647
    if bep is not None:
648
      idict["beparams"].update(bep)
649
    idict["osparams"] = cluster.SimpleFillOS(instance.os, instance.osparams)
650
    if osp is not None:
651
      idict["osparams"].update(osp)
652
    for nic in idict["nics"]:
653
      nic['nicparams'] = objects.FillDict(
654
        cluster.nicparams[constants.PP_DEFAULT],
655
        nic['nicparams'])
656
    return idict
657

    
658
  def _InstDictHvpBep(self, (instance, hvp, bep)):
659
    """Wrapper for L{_InstDict}.
660

661
    """
662
    return self._InstDict(instance, hvp=hvp, bep=bep)
663

    
664
  def _InstDictOsp(self, (instance, osparams)):
665
    """Wrapper for L{_InstDict}.
666

667
    """
668
    return self._InstDict(instance, osp=osparams)
669

    
670

    
671
class JobQueueRunner(_RpcClientBase, _generated_rpc.RpcClientJobQueue):
672
  """RPC wrappers for job queue.
673

674
  """
675
  def __init__(self, context, address_list):
676
    """Initializes this class.
677

678
    """
679
    if address_list is None:
680
      resolver = _SsconfResolver
681
    else:
682
      # Caller provided an address list
683
      resolver = _StaticResolver(address_list)
684

    
685
    _RpcClientBase.__init__(self, resolver, _ENCODERS.get,
686
                            lock_monitor_cb=context.glm.AddToLockMonitor)
687
    _generated_rpc.RpcClientJobQueue.__init__(self)
688

    
689

    
690
class BootstrapRunner(_RpcClientBase, _generated_rpc.RpcClientBootstrap):
691
  """RPC wrappers for bootstrapping.
692

693
  """
694
  def __init__(self):
695
    """Initializes this class.
696

697
    """
698
    _RpcClientBase.__init__(self, _SsconfResolver, _ENCODERS.get)
699
    _generated_rpc.RpcClientBootstrap.__init__(self)
700

    
701

    
702
class ConfigRunner(_RpcClientBase, _generated_rpc.RpcClientConfig):
703
  """RPC wrappers for L{config}.
704

705
  """
706
  def __init__(self, context, address_list):
707
    """Initializes this class.
708

709
    """
710
    if context:
711
      lock_monitor_cb = context.glm.AddToLockMonitor
712
    else:
713
      lock_monitor_cb = None
714

    
715
    if address_list is None:
716
      resolver = _SsconfResolver
717
    else:
718
      # Caller provided an address list
719
      resolver = _StaticResolver(address_list)
720

    
721
    _RpcClientBase.__init__(self, resolver, _ENCODERS.get,
722
                            lock_monitor_cb=lock_monitor_cb)
723
    _generated_rpc.RpcClientConfig.__init__(self)