class Reader(object): def __init__(self, host: str = 'localhost', port: int = 1883, timeout: int = 3, **kwargs): self._kvs = KVStore() node_id = self.node_id = self._kvs.get('node:node_id', '') self._host = host self._port = port # Note that the timeout is the time to wait for data, not for establishing a connection # A timeout for connection is not supported by the Paho MQTT library self._timeout = timeout self._client = mqtt.Client(client_id=CLIENT_ID_PREFIX + node_id, clean_session=False, **kwargs) self._client.enable_logger(logger=logger) # We store payloads from all topics in a dict. We need this since it's possible that # a payload comes in for a topic that's different to the one we're currently looking # to read, but we need to retain it so it can be returned as the result of another reading self._current_payloads = {} def __enter__(self): try: self._client.connect(self._host, port=self._port) except Exception: logger.error( 'Exception while attempting to connect to MQTT broker:') raise self._client.on_message = self.__on_message self._client.loop_start() sleep(self._timeout) return self def __exit__(self, type, value, traceback): try: self._client.disconnect() except Exception: logger.warning("Could not disconnect from MQTT broker", exc_info=True) def __on_message(self, client, userdata, msg): self._current_payloads[msg.topic] = msg.payload def read(self, topic, **rdg): res, _ = self._client.subscribe(topic, qos=DEFAULT_QOS) if res != mqtt.MQTT_ERR_SUCCESS: logger.error( f"Could not subscribe to topic '{topic}'. Result: {res}") return None return self._current_payloads.get(topic)
class Server(object): 'A server for the key-value store' def __init__(self, addr, addrs, avg_delays): self.addr = addr self.addrs = sorted(addrs) assert self.addr in addrs self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.data = KVStore() self.avg_delays = avg_delays def start(self): thread = Thread(target=self.run) thread.daemon = True thread.start() def send_message(self, q, msg, peer): 'Send `msg` to `peer` and wait for a response' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(peer) time.sleep(random.uniform(0, 2 * self.avg_delays[peer])) self.send(msg, sock) q.put(self.receive(sock)) def run(self): '''Listening for incoming connections and process them in a seperate thread.''' self.sock.bind(self.addr) self.sock.listen(5) while True: conn, addr = self.sock.accept() handler = Thread(target=self.handle_connection, args=(conn, addr)) handler.daemon = True handler.start() def replicas(self, key): h = hash(key) % len(self.addrs) return set(self.addrs[(h + i) % len(self.addrs)] for i in range(NUM_REPLICAS)) @classmethod def send(cls, obj, sock): 'Send the object `obj` over the socket `sock`' sock.send(pickle.dumps(obj)) @classmethod def receive(cls, sock): 'Receive a pickle-serialized object from the socket `sock`' data = '' while True: data += sock.recv(1024) try: return pickle.loads(data) except EOFError: pass # read more data def handle_connection(self, conn, addr): while True: msg = self.receive(conn) if isinstance(msg, GetRequest): value, timestamp = self.data.get(msg.key) self.send(GetResponse(msg.key, value, timestamp), conn) elif isinstance(msg, InsertRequest): result = self.data.insert(msg.key, msg.value) self.send(InsertResponse(msg.key, result), conn) elif isinstance(msg, UpdateRequest): result = self.data.update(msg.key, msg.value) self.send(UpdateResponse(msg.key, result), conn) elif isinstance(msg, DeleteRequest): result = self.data.delete(msg.key) self.send(DeleteResponse(msg.key, result), conn) elif isinstance(msg, RepairRequest): self.repair_val_timestamp(msg.key, msg.value, msg.timestamp) else: print "Unknown message type: {}".format(msg) def repair_val_timestamp(self, key, new_val, new_timestamp): local_pair = self.data.get(key) local_val = local_pair[0] local_timestamp = local_pair[1] # ignore repairs if key has been deleted/doesn't exist if local_pair and local_timestamp < new_timestamp: self.data.update(key, new_val, new_timestamp) def executeRepair(self, key, resp=None): result = resp if not result: result = self.get(key, "all") q = Queue() # send out repair requests to all replicas for a key, no need to block for replica in self.replicas(key): t = Thread(target=self.send_message, args=(q, RepairRequest(key, result.value, result.timestamp), replica)) t.start() def get(self, key, level): q = Queue() for replica in self.replicas(key): t = Thread(target=self.send_message, args=(q, GetRequest(key), replica)) t.start() if level == 'one': wait_for = 1 else: wait_for = NUM_REPLICAS responses = [] while len(responses) < wait_for: resp = q.get() assert isinstance(resp, GetResponse) assert resp.key == key responses.append(resp) max_response = max(responses, key=lambda r: r.timestamp) resp = None if level == 'one' else max_response t = Thread(target=self.executeRepair, args=(key, resp)) t.start() return max_response def insert(self, key, value, level): q = Queue() for replica in self.replicas(key): t = Thread(target=self.send_message, args=(q, InsertRequest(key, value), replica)) t.start() if level == 'one': wait_for = 1 else: wait_for = NUM_REPLICAS responses = [] while len(responses) < wait_for: resp = q.get() assert isinstance(resp, InsertResponse) assert resp.key == key responses.append(resp) return all(r.result for r in responses) def update(self, key, value, level): q = Queue() for replica in self.replicas(key): t = Thread(target=self.send_message, args=(q, UpdateRequest(key, value), replica)) t.start() if level == 'one': wait_for = 1 else: wait_for = NUM_REPLICAS responses = [] while len(responses) < wait_for: resp = q.get() assert isinstance(resp, UpdateResponse) assert resp.key == key responses.append(resp) return all(r.result for r in responses) def delete(self, key): q = Queue() for replica in self.replicas(key): t = Thread(target=self.send_message, args=(q, DeleteRequest(key), replica)) t.start() responses = [] while len(responses) < NUM_REPLICAS: resp = q.get() assert isinstance(resp, DeleteResponse) assert resp.key == key responses.append(resp) return all(r.result for r in responses) def items(self): for key, (value, timestamp) in self.data.items(): yield key, value def owners(self, key): 'Return a list of servers responsible for `key`' return self.replicas(key)
def initialize(wifi_ap: WifiAPSnapCtl, kvs: KVStore) -> bool: wifi_ap_cfg = kvs.get(KVS_CONFIG_KEY) return wifi_ap.configure(wifi_ap_cfg)