import { range } from 'lodash' ; import { getAllUsedNodePorts , getService } from '../kubernetes' ; import { BaseFullNodeDeployer , BaseFullNodeDeploymentConfig } from './base' ; const NODE_PORT_MIN = 30000 ; const NODE_PORT_MAX = 32767 ; export abstract class BaseNodePortFullNodeDeployer extends BaseFullNodeDeployer { async additionalHelmParameters ( ) { const existingNodePortSet = await this . getExistingNodePortSet ( ) ; const newNodePortForEachFullNode = await this . getNodePortForEachFullNode ( ) ; const newNodePortSet = new Set ( newNodePortForEachFullNode ) ; const nodePortsToRemove = new Set ( Array . from ( existingNodePortSet ) . filter ( ( existing ) => ! newNodePortSet . has ( existing ) ) ) ; await this . setIngressRulesTCPAndUDP ( newNodePortForEachFullNode , true ) ; await this . setIngressRulesTCPAndUDP ( Array . from ( nodePortsToRemove ) , false ) ; const nodePortPerFullNodeStrs = newNodePortForEachFullNode . map ( ( nodePort , index ) => `--set geth.service_node_port_per_full_node[${ index }]=${ nodePort }` ) ; return [ ... nodePortPerFullNodeStrs , `--set geth.service_type=NodePort` ] ; } async getNodePortForEachFullNode ( ) { const allUsedNodePorts = await getAllUsedNodePorts ( ) ; const serviceForEachFullNode = await this . getServiceForEachFullNode ( ) ; const NO_NODE_PORT = - 1 ; const nodePortForEachFullNode = serviceForEachFullNode . map ( ( service ) => { if ( ! service ) { return NO_NODE_PORT ; } return service . spec . ports . reduce ( ( existingNodePort , portsSpec ) => { if ( ! portsSpec . nodePort ) { return existingNodePort ; } if ( existingNodePort !== NO_NODE_PORT && existingNodePort !== portsSpec . nodePort ) { throw Error ( `Expected all nodePorts to be the same in service, got ${ existingNodePort } !== ${ portsSpec . nodePort }` ) ; } return portsSpec . nodePort ; } , NO_NODE_PORT ) ; } ) ; let potentialPort = NODE_PORT_MIN ; let allUsedNodePortsIndex = 0 ; for ( let i = 0 ; i < nodePortForEachFullNode . length ; i ++ ) { const nodePort = nodePortForEachFullNode [ i ] ; if ( nodePort === NO_NODE_PORT ) { for ( ; allUsedNodePortsIndex < allUsedNodePorts . length ; allUsedNodePortsIndex ++ ) { if ( potentialPort > NODE_PORT_MAX ) { throw Error ( `No available node ports` ) ; } const usedPort = allUsedNodePorts [ allUsedNodePortsIndex ] ; if ( potentialPort < usedPort ) { break ; } potentialPort = usedPort + 1 ; } nodePortForEachFullNode [ i ] = potentialPort ; allUsedNodePorts . splice ( allUsedNodePortsIndex , 0 , potentialPort ) ; potentialPort ++ ; } } return nodePortForEachFullNode ; } /**
     * Not needed for NodePort services. Instead, shows a message to remove any
     * now-unused ports from the security group whitelist
     */ async deallocateAllIPs ( ) { } /**
     * Returns an array with each element as the corresponding full node's service.
     * An element will be undefined if the service doesn't exist.
     */ getServiceForEachFullNode ( ) { const replicas = this . deploymentConfig . replicas ; return Promise . all ( range ( replicas ) . map ( async ( i ) => getService ( `${ this . celoEnv }-fullnodes-${ i }` , this . kubeNamespace ) ) ) ; } /**
     * Returns an array of all services that currently exist for full nodes.
     * Does so using a selector, and has no guarantees about the order of the services.
     */ async getExistingFullNodeServices ( ) { const response = await getService ( `--selector=component=celo-fullnode-protocol-traffic` , this . kubeNamespace ) ; return response . items ; } /**
     * Looks at the existing full node services and returns which nodePorts are currently used.
     */ async getExistingNodePortSet ( ) { const serviceForEachFullNode = await this . getExistingFullNodeServices ( ) ; return serviceForEachFullNode . reduce ( ( set , service ) => { if ( ! service ) { return set ; } for ( const portSpec of service . spec . ports ) { if ( portSpec . nodePort ) { set . add ( portSpec . nodePort ) ; } } return set ; } , new Set < number > ( ) ) ; } async getFullNodeIP ( _index ) { throw Error ( 'Not supported for NodePort full nodes' ) ; } /**
     * Determines if a given port number is a valid node port.
     */ isNodePort ( portNumber ) { return portNumber >= NODE_PORT_MIN && portNumber <= NODE_PORT_MAX ; } abstract setIngressRulesTCPAndUDP ( nodePorts , authorize ) ; get deploymentConfig ( ) : BaseFullNodeDeploymentConfig { return this . _deploymentConfig ; } }