def SendRequest(self, request, args=None, coverage=0, async_=True): """Send a confd request to some MCs @type request: L{objects.ConfdRequest} @param request: the request to send @type args: tuple @param args: additional callback arguments @type coverage: integer @param coverage: number of remote nodes to contact; if default (0), it will use a reasonable default (L{ganeti.constants.CONFD_DEFAULT_REQ_COVERAGE}), if -1 is passed, it will use the maximum number of peers, otherwise the number passed in will be used @type async_: boolean @param async_: handle the write asynchronously """ if coverage == 0: coverage = min(len(self._peers), constants.CONFD_DEFAULT_REQ_COVERAGE) elif coverage == -1: coverage = len(self._peers) if coverage > len(self._peers): raise errors.ConfdClientError("Not enough MCs known to provide the" " desired coverage") if not request.rsalt: raise errors.ConfdClientError("Missing request rsalt") self.ExpireRequests() if request.rsalt in self._requests: raise errors.ConfdClientError("Duplicate request rsalt") if request.type not in constants.CONFD_REQS: raise errors.ConfdClientError("Invalid request type") random.shuffle(self._peers) targets = self._peers[:coverage] now = time.time() payload = self._PackRequest(request, now=now) for target in targets: try: self._socket.enqueue_send(target, self._confd_port, payload) except errors.UdpDataSizeError: raise errors.ConfdClientError("Request too big") expire_time = now + constants.CONFD_CLIENT_EXPIRE_TIMEOUT self._requests[request.rsalt] = _Request(request, args, expire_time, targets) if not async_: self.FlushSendQueue()
def _SetPeersAddressFamily(self): if not self._peers: raise errors.ConfdClientError("Peer list empty") try: peer = self._peers[0] self._family = netutils.IPAddress.GetAddressFamily(peer) for peer in self._peers[1:]: if netutils.IPAddress.GetAddressFamily(peer) != self._family: raise errors.ConfdClientError("Peers must be of same address family") except errors.IPAddressError: raise errors.ConfdClientError("Peer address %s invalid" % peer)
def __init__(self, **kwargs): objects.ConfdRequest.__init__(self, **kwargs) if not self.rsalt: self.rsalt = utils.NewUUID() if not self.protocol: self.protocol = constants.CONFD_PROTOCOL_VERSION if self.type not in constants.CONFD_REQS: raise errors.ConfdClientError("Invalid request type")
class ConfdClient(object): """Send queries to confd, and get back answers. Since the confd model works by querying multiple master candidates, and getting back answers, this is an asynchronous library. It can either work through asyncore or with your own handling. @type _requests: dict @ivar _requests: dictionary indexes by salt, which contains data about the outstanding requests; the values are objects of type L{_Request} """ def __init__(self, hmac_key, peers, callback, port=None, logger=None): """Constructor for ConfdClient @type hmac_key: string @param hmac_key: hmac key to talk to confd @type peers: list @param peers: list of peer nodes @type callback: f(L{ConfdUpcallPayload}) @param callback: function to call when getting answers @type port: integer @param port: confd port (default: use GetDaemonPort) @type logger: logging.Logger @param logger: optional logger for internal conditions """ if not callable(callback): raise errors.ProgrammerError("callback must be callable") self.UpdatePeerList(peers) self._SetPeersAddressFamily() self._hmac_key = hmac_key self._socket = ConfdAsyncUDPClient(self, self._family) self._callback = callback self._confd_port = port self._logger = logger self._requests = {} if self._confd_port is None: self._confd_port = netutils.GetDaemonPort(constants.CONFD) def UpdatePeerList(self, peers): """Update the list of peers @type peers: list @param peers: list of peer nodes """ # we are actually called from init, so: # pylint: disable=W0201 if not isinstance(peers, list): raise errors.ProgrammerError("peers must be a list") # make a copy of peers, since we're going to shuffle the list, later self._peers = list(peers) def _PackRequest(self, request, now=None): """Prepare a request to be sent on the wire. This function puts a proper salt in a confd request, puts the proper salt, and adds the correct magic number. """ if now is None: now = time.time() tstamp = "%d" % now req = serializer.DumpSignedJson(request.ToDict(), self._hmac_key, tstamp) return confd.PackMagic(req) def _UnpackReply(self, payload): in_payload = confd.UnpackMagic(payload) (dict_answer, salt) = serializer.LoadSignedJson(in_payload, self._hmac_key) answer = objects.ConfdReply.FromDict(dict_answer) return answer, salt def ExpireRequests(self): """Delete all the expired requests. """ now = time.time() for rsalt, rq in self._requests.items(): if now >= rq.expiry: del self._requests[rsalt] client_reply = ConfdUpcallPayload( salt=rsalt, type=UPCALL_EXPIRE, orig_request=rq.request, extra_args=rq.args, client=self, ) self._callback(client_reply) def SendRequest(self, request, args=None, coverage=0, async=True): """Send a confd request to some MCs @type request: L{objects.ConfdRequest} @param request: the request to send @type args: tuple @param args: additional callback arguments @type coverage: integer @param coverage: number of remote nodes to contact; if default (0), it will use a reasonable default (L{ganeti.constants.CONFD_DEFAULT_REQ_COVERAGE}), if -1 is passed, it will use the maximum number of peers, otherwise the number passed in will be used @type async: boolean @param async: handle the write asynchronously """ if coverage == 0: coverage = min(len(self._peers), constants.CONFD_DEFAULT_REQ_COVERAGE) elif coverage == -1: coverage = len(self._peers) if coverage > len(self._peers): raise errors.ConfdClientError("Not enough MCs known to provide the" " desired coverage") if not request.rsalt: raise errors.ConfdClientError("Missing request rsalt") self.ExpireRequests() if request.rsalt in self._requests: raise errors.ConfdClientError("Duplicate request rsalt") if request.type not in constants.CONFD_REQS: raise errors.ConfdClientError("Invalid request type") random.shuffle(self._peers) targets = self._peers[:coverage] now = time.time() payload = self._PackRequest(request, now=now) for target in targets: try: self._socket.enqueue_send(target, self._confd_port, payload) except errors.UdpDataSizeError: raise errors.ConfdClientError("Request too big") expire_time = now + constants.CONFD_CLIENT_EXPIRE_TIMEOUT self._requests[request.rsalt] = _Request(request, args, expire_time, targets) if not async: self.FlushSendQueue()