def on_put(self, req, resp, name): """ Handles the creation of a new Cluster. :param req: Request instance that will be passed through. :type req: falcon.Request :param resp: Response instance that will be passed through. :type resp: falcon.Response :param name: The name of the Cluster being created. :type name: str """ # PUT is idempotent, and since there's no body to this request, # there's nothing to conflict with. The request should always # succeed, even if we didn't actually do anything. try: Cluster.retrieve(name) self.logger.info( 'Creation of already exisiting cluster {0} requested.'.format( name)) except: pass cluster = Cluster(status='ok', hostset=[]) cluster.save(name) self.logger.info( 'Created cluster {0} per request.'.format(name)) resp.status = falcon.HTTP_201
def etcd_cluster_add_host(name, address): """ Adds a host address to a cluster with the given name. If no such cluster exists, the function raises KeyError. Note the function is idempotent: if the host address is already in the cluster, no change occurs. :param name: Name of a cluster :type name: str :param address: Host address to add :type address: str """ try: cluster = Cluster.retrieve(name) except: raise KeyError # FIXME: Need input validation. # - Does the host exist at /commissaire/hosts/{IP}? # - Does the host already belong to another cluster? # FIXME: Should guard against races here, since we're fetching # the cluster record and writing it back with some parts # unmodified. Use either locking or a conditional write # with the etcd 'modifiedIndex'. Deferring for now. if address not in cluster.hostset: cluster.hostset.append(address) cluster.save(name)
def on_get(self, req, resp, name, address): """ Handles GET requests for individual hosts in a Cluster. This is a membership test, returning 200 OK if the host address is part of the cluster, or else 404 Not Found. :param req: Request instance that will be passed through. :type req: falcon.Request :param resp: Response instance that will be passed through. :type resp: falcon.Response :param name: The name of the Cluster being requested. :type name: str :param address: The address of the Host being requested. :type address: str """ try: cluster = Cluster.retrieve(name) except: resp.status = falcon.HTTP_404 return if address in cluster.hostset: resp.status = falcon.HTTP_200 else: resp.status = falcon.HTTP_404
def on_get(self, req, resp, name): """ Handles retrieval of an existing Cluster. :param req: Request instance that will be passed through. :type req: falcon.Request :param resp: Response instance that will be passed through. :type resp: falcon.Response :param name: The name of the Cluster being requested. :type name: str """ try: cluster = Cluster.retrieve(name) # key = util.etcd_cluster_key(name) # etcd_resp, error = cherrypy.engine.publish('store-get', key)[0] except: resp.status = falcon.HTTP_404 return if not cluster: resp.status = falcon.HTTP_404 return self._calculate_hosts(cluster) # Have to set resp.body explicitly to include Hosts. resp.body = cluster.to_json_with_hosts() resp.status = falcon.HTTP_200
def on_put(self, req, resp, name): """ Handles PUT requests for Cluster hosts. This replaces the entire host list for a Cluster. :param req: Request instance that will be passed through. :type req: falcon.Request :param resp: Response instance that will be passed through. :type resp: falcon.Response :param name: The name of the Cluster being requested. :type name: str """ try: req_body = json.loads(req.stream.read().decode()) old_hosts = set(req_body['old']) # Ensures no duplicates new_hosts = set(req_body['new']) # Ensures no duplicates except (KeyError, TypeError): self.logger.info( 'Bad client PUT request for cluster {0}: {1}'. format(name, req_body)) resp.status = falcon.HTTP_400 return try: cluster = Cluster.retrieve(name) except: resp.status = falcon.HTTP_404 return # old_hosts must match current hosts to accept new_hosts. # Note: Order doesn't matter, so etcd's atomic comparison # of the raw values would be too strict. if old_hosts != set(cluster.hostset): self.logger.info( 'Conflict setting hosts for cluster {0}'.format(name)) resp.status = falcon.HTTP_409 return # FIXME: Need input validation. For each new host, # - Does the host exist at /commissaire/hosts/{IP}? # - Does the host already belong to another cluster? # FIXME: Should guard against races here, since we're fetching # the cluster record and writing it back with some parts # unmodified. Use either locking or a conditional write # with the etcd 'modifiedIndex'. Deferring for now. cluster.hostset = list(new_hosts) cluster.save(name) resp.status = falcon.HTTP_200
def etcd_cluster_has_host(name, address): """ Checks if a host address belongs to a cluster with the given name. If no such cluster exists, the function raises KeyError. :param name: Name of a cluster :type name: str :param address: Host address :type address: str """ try: cluster = Cluster.retrieve(name) except: raise KeyError return address in cluster.hostset
def on_get(self, req, resp, name): """ Handles GET requests for Cluster hosts. :param req: Request instance that will be passed through. :type req: falcon.Request :param resp: Response instance that will be passed through. :type resp: falcon.Response :param name: The name of the Cluster being requested. :type name: str """ try: cluster = Cluster.retrieve(name) except: resp.status = falcon.HTTP_404 return resp.body = json.dumps(cluster.hostset) resp.status = falcon.HTTP_200
def on_delete(self, req, resp, address): """ Handles the Deletion of a Host. :param req: Request instance that will be passed through. :type req: falcon.Request :param resp: Response instance that will be passed through. :type resp: falcon.Response :param address: The address of the Host being requested. :type address: str """ resp.body = "{}" try: Host.delete(address) resp.status = falcon.HTTP_200 except: resp.status = falcon.HTTP_404 # Also remove the host from all clusters. # Note: We've done all we need to for the host deletion, # so if an error occurs from here just log it and # return. try: clusters = Clusters.retrieve() except: self.logger.warn("Etcd does not have any clusters") return try: for cluster_name in clusters.clusters: self.logger.debug("Checking cluster {0}".format(cluster_name)) cluster = Cluster.retrieve(cluster_name) if address in cluster.hostset: self.logger.info("Removing {0} from cluster {1}".format(address, cluster_name)) cluster.hostset.remove(address) cluster.save(cluster_name) self.logger.info("{0} has been removed from cluster {1}".format(address, cluster_name)) except: self.logger.warn("Failed to remove {0} from cluster {1}".format(address, cluster_name))