def add_cluster_member(message, bus): """ Adds a member to the cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: address = message['params']['host'] name = message['params']['name'] except KeyError as error: return create_jsonrpc_error( message, error, JSONRPC_ERRORS['BAD_REQUEST']) try: host = bus.storage.get_host(address) cluster = bus.storage.get_cluster(name) except _bus.StorageLookupError as error: return create_jsonrpc_error( message, error, JSONRPC_ERRORS['NOT_FOUND']) if host.address not in cluster.hostset: # FIXME: Need more input validation. # - 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 host_suitable_for_cluster(host): cluster.hostset.append(host.address) bus.storage.save(cluster) # Register new host with the cluster's container manager # (if applicable), and update its status. update_new_cluster_member_status(bus, cluster, host) else: msg = ( 'Host {} (status: {}) not ready to join cluster ' '"{}"'.format(host.address, host.status, cluster.name)) LOGGER.error(msg) return create_jsonrpc_error( message, msg, JSONRPC_ERRORS['METHOD_NOT_ALLOWED']) # Return back the host in a list return create_jsonrpc_response(message['id'], [host.address])
def update_cluster_members(message, bus): """ Updates the list of members in a cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: old_hosts = set(message['params']['old']) # Ensures no duplicates new_hosts = set(message['params']['new']) # Ensures no duplicates LOGGER.debug('old_hosts="{}", new_hosts="{}"'.format( old_hosts, new_hosts)) except Exception as error: return return_error(message, error, JSONRPC_ERRORS['BAD_REQUEST']) try: cluster = bus.request( 'storage.get', params=['Cluster', { 'name': message['params']['name'] }, True]) except Exception as error: return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) if old_hosts != set(cluster['result']['hostset']): msg = 'Conflict setting hosts for cluster {0}'.format( cluster['result']['name']) LOGGER.error(msg) return return_error(message, msg, JSONRPC_ERRORS['CONFLICT']) # 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['result']['hostset'] = list(new_hosts) cluster = models.Cluster.new(**cluster['result']) cluster._validate() response = bus.request('storage.save', params=['Cluster', cluster.to_dict()]) return create_response(message['id'], response['result'])
def update_cluster_members(message, bus): """ Updates the list of members in a cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: old_hosts = set(message['params']['old']) # Ensures no duplicates new_hosts = set(message['params']['new']) # Ensures no duplicates LOGGER.debug('old_hosts="%s", new_hosts="%s"', old_hosts, new_hosts) except Exception as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['BAD_REQUEST']) try: name = message['params']['name'] cluster = bus.storage.get_cluster(name) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) if old_hosts != set(cluster.hostset): msg = 'Conflict setting hosts for cluster {}'.format(name) LOGGER.error(msg) return create_jsonrpc_error(message, msg, JSONRPC_ERRORS['CONFLICT']) # FIXME: Need more input validation. For each new host, # - Does the host already belong to another cluster? # Only verify *new* hosts are suitable to add to the cluster. # Rejecting existing cluster members would be surprising to users. actual_new_hosts = new_hosts.difference(old_hosts) LOGGER.debug('Checking status of new hosts (ignoring existing): %s', ', '.join(actual_new_hosts)) list_of_hosts = bus.storage.get_many( [models.Host.new(address=x) for x in actual_new_hosts]) hosts_not_ready = [ host.address for host in list_of_hosts if not host_suitable_for_cluster(host) ] if hosts_not_ready: msg = 'Hosts not ready to join cluster "{}": {}'.format( name, ','.join(hosts_not_ready)) LOGGER.error(msg) return create_jsonrpc_error(message, msg, JSONRPC_ERRORS['METHOD_NOT_ALLOWED']) # 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) saved_cluster = bus.storage.save(cluster) # Register newly added hosts with the cluster's container manager # (if applicable), and update their status. update_new_cluster_member_status(bus, cluster, *list_of_hosts) # XXX Using to_dict() instead of to_dict_safe() to include hostset. return create_jsonrpc_response(message['id'], saved_cluster.to_dict())