import { ITextAndBadge } from 'src/app/Utils/ValueResolver' ; import { DataService } from 'src/app/services/data.service' ; import { Node } from '../Node' ; import { IClusterHealthChunk } from '../../HealthChunkRawDataTypes' ; import { IdGenerator } from 'src/app/Utils/IdGenerator' ; import { map } from 'rxjs/operators' ; import { IResponseMessageHandler } from 'src/app/Common/ResponseMessageHandlers' ; import { Observable , of } from 'rxjs' ; import { Utils } from 'src/app/Utils/Utils' ; import { HealthStateConstants , NodeStatusConstants , StatusWarningLevel , BannerWarningID } from 'src/app/Common/Constants' ; import { DataModelCollectionBase } from './CollectionBase' ; import { RoutesService } from 'src/app/services/routes.service' ; export interface INodesStatusDetails { nodeType : string ; statusTypeCounts : Record < string , number > ; warningCount : number ; errorCount : number ; okCount : number ; totalCount : number ; } export class NodeStatusDetails implements INodesStatusDetails { public static readonly allNodeText = 'All Nodes' ; public static readonly allSeedNodesText = 'Seed Nodes' ; public nodeType : string ; public statusTypeCounts : Record < string , number > ; public warningCount = 0 ; public errorCount = 0 ; public totalCount = 0 ; public okCount = 0 ; public nodes : Node [ ] = [ ] ; public constructor ( nodeType ) { this . nodeType = nodeType ; this . statusTypeCounts = { } ; this . statusTypeCounts [ NodeStatusConstants . Up ] = 0 ; this . statusTypeCounts [ NodeStatusConstants . Down ] = 0 ; this . statusTypeCounts [ NodeStatusConstants . Enabling ] = 0 ; this . statusTypeCounts [ NodeStatusConstants . Disabling ] = 0 ; this . statusTypeCounts [ NodeStatusConstants . Disabled ] = 0 ; this . statusTypeCounts [ NodeStatusConstants . Unknown ] = 0 ; this . statusTypeCounts [ NodeStatusConstants . Invalid ] = 0 ; } public add ( node ) { this . statusTypeCounts [ node . raw . NodeStatus ] ++ ; this . totalCount ++ ; if ( node . healthState . text === HealthStateConstants . Warning ) { this . warningCount ++ ; } if ( node . healthState . text === HealthStateConstants . Error ) { this . errorCount ++ ; } if ( node . healthState . text === HealthStateConstants . OK ) { this . okCount ++ ; } this . nodes . push ( node ) ; } } export class NodeCollection extends DataModelCollectionBase < Node > { private static checkedOneNodeScenario = false ; public healthState : ITextAndBadge ; public upgradeDomains : string [ ] ; public faultDomains : string [ ] ; public healthySeedNodes : string ; public seedNodeCount : number ; public disabledAndDisablingCount : number ; public disabledAndDisablingNodes : Node [ ] ; public nodeTypes : string [ ] ; public constructor ( data ) { super ( data ) ; } public get viewPath ( ) : string { return RoutesService . getNodesViewPath ( ) ; } public mergeClusterHealthStateChunk ( clusterHealthChunk ) { return this . updateCollectionFromHealthChunkList ( clusterHealthChunk . NodeHealthStateChunks , item => IdGenerator . node ( item . NodeName ) ) . pipe ( map ( ( ) => { this . updateNodesHealthState ( ) ; } ) ) ; } public getNodeStateCounts ( includeAllNodes = true , includeSeedNoddes = true ) { const counts = { } ; const allNodes = new NodeStatusDetails ( NodeStatusDetails . allNodeText ) ; const seedNodes = new NodeStatusDetails ( NodeStatusDetails . allSeedNodesText ) ; this . collection . forEach ( node => { if ( node . raw . IsSeedNode ) { seedNodes . add ( node ) ; } if ( ! ( node . raw . Type in counts ) ) { counts [ node . raw . Type ] = new NodeStatusDetails ( node . raw . Type ) ; } counts [ node . raw . Type ] . add ( node ) ; allNodes . add ( node ) ; } ) ; const resultList = [ ] ; if ( includeAllNodes ) { resultList . push ( allNodes ) ; } if ( includeSeedNoddes ) { resultList . push ( seedNodes ) ; } const nodeTypes = Object . keys ( counts ) . map ( key => counts [ key ] ) . sort ( ( a , b ) => a . nodeType . localeCompare ( b . nodeType ) ) ; return resultList . concat ( nodeTypes ) ; } protected get indexPropery ( ) : string { return 'name' ; } protected retrieveNewCollection ( messageHandler ? ) { return this . data . restClient . getNodes ( messageHandler ) . pipe ( map ( items => { return items . map ( raw => new Node ( this . data , raw ) ) ; } ) ) ; } protected updateInternal ( ) { this . updateNodesHealthState ( ) ; this . faultDomains = this . collection . map ( node => node . raw . FaultDomain ) ; this . faultDomains = Utils . unique ( this . faultDomains ) . sort ( ) ; this . upgradeDomains = this . collection . map ( node => node . raw . UpgradeDomain ) ; this . upgradeDomains = Utils . unique ( this . upgradeDomains ) . sort ( ) ; const seedNodes = this . collection . filter ( node => node . raw . IsSeedNode ) ; const healthyNodes = seedNodes . filter ( node => node . healthState . text === HealthStateConstants . OK ) ; const nodeTypes = new Set < string > ( ) ; let disabledNodes = 0 ; let disablingNodes = 0 ; const disabled = [ ] ; const disabling = [ ] ; this . collection . forEach ( node => { if ( node . raw . NodeStatus === NodeStatusConstants . Disabled ) { disabledNodes ++ ; disabled . push ( node ) ; } if ( node . raw . NodeStatus === NodeStatusConstants . Disabling ) { disablingNodes ++ ; disabling . push ( node ) ; } nodeTypes . add ( node . raw . Type ) ; } ) ; this . disabledAndDisablingNodes = disabling . concat ( disabled ) ; this . seedNodeCount = seedNodes . length ; this . disabledAndDisablingCount = disabledNodes + disablingNodes ; this . checkOneNodeScenario ( ) ; this . healthySeedNodes = seedNodes . length . toString ( ) + ' (' + Math . round ( healthyNodes . length / seedNodes . length * 100 ) . toString ( ) + '%)' ; this . nodeTypes = Array . from ( nodeTypes ) ; return of ( true ) ; } public checkSeedNodeCount ( expected ) { if ( this . seedNodeCount === 0 ) { return ; } if ( this . seedNodeCount < expected && this . seedNodeCount !== 1 ) { this . data . warnings . addOrUpdateNotification ( { message : `This cluster is currently running on the bronze reliability tier. For production workloads, only a reliability level of silver or greater is supported ` , level : StatusWarningLevel . Warning , priority : 2 , id : BannerWarningID . ClusterDegradedState , link : 'https://aka.ms/servicefabric/reliability' , linkText : 'Read here for more guidance' , confirmText : `This cluster is currently running on the bronze reliability tier which indicates a test/staging environment. For production workloads, only a reliability
                level of silver or greater is supported. For more information on the reliability characteristics of a cluster, please see https://aka.ms/sfreliabilitytiers` } ) ; } else { this . data . warnings . removeNotificationById ( BannerWarningID . ClusterDegradedState ) ; } } private checkOneNodeScenario ( ) { if ( ! NodeCollection . checkedOneNodeScenario && this . collection . length === 1 ) { this . data . warnings . addOrUpdateNotification ( { message : 'One node cluster is considered a test cluster and cannot perform cluster upgrades.' , level : StatusWarningLevel . Info , priority : 1 , id : BannerWarningID . OneNodeCluster , link : 'https://aka.ms/servicefabric/durability' } ) ; } NodeCollection . checkedOneNodeScenario = true ; } private updateNodesHealthState ( ) { this . healthState = this . valueResolver . resolveHealthStatus ( Utils . max ( this . collection . map ( node => HealthStateConstants . Values [ node . healthState . text ] ) ) . toString ( ) ) ; } }