diff --git a/.github/workflows/build-test-monitor-cluster-jdk21.yaml b/.github/workflows/build-test-monitor-cluster-jdk21.yaml new file mode 100644 index 0000000..b1adaf4 --- /dev/null +++ b/.github/workflows/build-test-monitor-cluster-jdk21.yaml @@ -0,0 +1,77 @@ +# Copyright 2021, 2024 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence CLI GitHub Actions CI build - Test Monitor Cluster JDK21 +# --------------------------------------------------------------------------- +name: CI Test Monitor Cluster JDK21 + +on: + workflow_dispatch: + push: + branches-ignore: + - gh-pages + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 22.06.8 + - 24.03 + javaVersion: + - 21 + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.javaVersion }} + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.20' + + - name: Build cohctl + shell: bash + run: make cohctl + + - name: Test Monitor Cluster + shell: bash + run: | + sudo apt-add-repository universe -y + sudo apt-get install expect -y + COHERENCE_VERSION=${{ matrix.coherenceVersion }} make test-monitor-cluster + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.coherenceVersion }}-${{ matrix.javaVersion }} + path: build/_output/test-logs diff --git a/Makefile b/Makefile index c257276..62699b6 100644 --- a/Makefile +++ b/Makefile @@ -505,6 +505,13 @@ test-discovery: test-clean gotestsum $(BUILD_PROPS) ## Run Discovery tests with test-create-cluster: test-clean gotestsum $(BUILD_PROPS) ## Run create cluster tests ./scripts/test-create-cluster.sh $(COHERENCE_VERSION) +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the Go monitor cluster tests for standalone Coherence +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-monitor-cluster +test-monitor-cluster: test-clean gotestsum $(BUILD_PROPS) ## Run monitor cluster tests + ./scripts/test-monitor-cluster.sh $(COHERENCE_VERSION) + # ---------------------------------------------------------------------------------------------------------------------- # Executes the Go end to end tests and unit tests for standalone Coherence with coverage # ---------------------------------------------------------------------------------------------------------------------- diff --git a/pkg/cmd/cluster_utils.go b/pkg/cmd/cluster_utils.go index 63a1877..e1151e5 100644 --- a/pkg/cmd/cluster_utils.go +++ b/pkg/cmd/cluster_utils.go @@ -746,8 +746,27 @@ type clusterSummaryInfo struct { executors config.Executors } +// the following constants determine if the retrieveClusterSummary should retrieve the data +const ( + memberPanelData = "members" + cachesPanelData = "caches" + elasticDataPanelData = "elastic-data" + executorsPanelData = "executors" + healthPanelData = "health" + federationPanelData = "federation" + proxiesPanelData = "proxies" + storagePanelData = "storage" + servicesPanelData = "services" + reportersPanelData = "reporters" + topicsPanelData = "topics" + httpSessionsPanelData = "http-sessions" +) + // retrieveClusterSummary retrieves all the required information used by various commands. -func retrieveClusterSummary(dataFetcher fetcher.Fetcher) (clusterSummaryInfo, []error) { +// panelData contains an array of data items to retrieve. If len(panelData) == 0 then this +// means to retrieve all data, otherwise only retrieve data which is contained in data array. +// this is to lessen the load on management HTTP server when using monitor cluster command. +func retrieveClusterSummary(dataFetcher fetcher.Fetcher, panelData ...string) (clusterSummaryInfo, []error) { var ( errorSink = createErrorSink() waitGroupCount = 13 @@ -773,14 +792,16 @@ func retrieveClusterSummary(dataFetcher fetcher.Fetcher) (clusterSummaryInfo, [] err error data []byte ) - data, err = dataFetcher.GetMemberDetailsJSON(false) - if err != nil { - errorSink.AppendError(err) - return + if shouldRetrieve(memberPanelData, panelData) { + data, err = dataFetcher.GetMemberDetailsJSON(false) + if err != nil { + errorSink.AppendError(err) + return + } + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.membersResult = data } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.membersResult = data }() go func() { @@ -789,28 +810,30 @@ func retrieveClusterSummary(dataFetcher fetcher.Fetcher) (clusterSummaryInfo, [] data []byte machines []config.Machine ) - machinesMap, err := GetMachineList(dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } + if shouldRetrieve(memberPanelData, panelData) { + machinesMap, err := GetMachineList(dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } - machines, err = getMachines(machinesMap, dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } + machines, err = getMachines(machinesMap, dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } - data, err = getOSJson(machinesMap, dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } + data, err = getOSJson(machinesMap, dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.machines = machines - result.machinesData = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.machines = machines + result.machinesData = data + } }() go func() { @@ -821,93 +844,103 @@ func retrieveClusterSummary(dataFetcher fetcher.Fetcher) (clusterSummaryInfo, [] return } - var services = config.ServicesSummaries{} - err = json.Unmarshal(data, &services) - if err != nil { - errorSink.AppendError(err) - return - } + if shouldRetrieve(servicesPanelData, panelData) { + var services = config.ServicesSummaries{} + err = json.Unmarshal(data, &services) + if err != nil { + errorSink.AppendError(err) + return + } - serviceList := GetListOfCacheServices(services) + serviceList := GetListOfCacheServices(services) - allCachesSummary, err := getCaches(serviceList, dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } + allCachesSummary, err := getCaches(serviceList, dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.servicesResult = data - result.cacheSummaryDetail = allCachesSummary - result.serviceList = serviceList + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.servicesResult = data + result.cacheSummaryDetail = allCachesSummary + result.serviceList = serviceList + } }() go func() { defer wg.Done() - topicsDetails, err := getTopics(dataFetcher, serviceName) - if err != nil { - errorSink.AppendError(err) - return - } + if shouldRetrieve(topicsPanelData, panelData) { + topicsDetails, err := getTopics(dataFetcher, serviceName) + if err != nil { + errorSink.AppendError(err) + return + } - topicsMemberDetails, err := getTopicsMembers(dataFetcher, topicsDetails) - if err != nil { - errorSink.AppendError(err) - return - } + topicsMemberDetails, err := getTopicsMembers(dataFetcher, topicsDetails) + if err != nil { + errorSink.AppendError(err) + return + } - topicsSubscriberDetails, err := getTopicsSubscribers(dataFetcher, topicsDetails) - if err != nil { - errorSink.AppendError(err) - return - } + topicsSubscriberDetails, err := getTopicsSubscribers(dataFetcher, topicsDetails) + if err != nil { + errorSink.AppendError(err) + return + } - enrichTopicsSummary(&topicsDetails, topicsMemberDetails, topicsSubscriberDetails) + enrichTopicsSummary(&topicsDetails, topicsMemberDetails, topicsSubscriberDetails) - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.topicsDetails = topicsDetails - result.topicsMemberDetails = topicsMemberDetails - result.topicsSubscriberDetails = topicsSubscriberDetails + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.topicsDetails = topicsDetails + result.topicsMemberDetails = topicsMemberDetails + result.topicsSubscriberDetails = topicsSubscriberDetails + } }() go func() { defer wg.Done() - data, err := dataFetcher.GetMembersHealth() - if err != nil { - errorSink.AppendError(err) - return - } + if shouldRetrieve(healthPanelData, panelData) { + data, err := dataFetcher.GetMembersHealth() + if err != nil { + errorSink.AppendError(err) + return + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.healthResult = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.healthResult = data + } }() go func() { defer wg.Done() - data, err := dataFetcher.GetStorageDetailsJSON() - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(storagePanelData, panelData) { + data, err := dataFetcher.GetStorageDetailsJSON() + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.storageData = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.storageData = data + } }() go func() { defer wg.Done() - data, err := dataFetcher.GetProxySummaryJSON() - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(proxiesPanelData, panelData) { + data, err := dataFetcher.GetProxySummaryJSON() + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.proxyResults = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.proxyResults = data + } }() go func() { @@ -915,87 +948,99 @@ func retrieveClusterSummary(dataFetcher fetcher.Fetcher) (clusterSummaryInfo, [] if !verboseOutput && !monitorCluster { return } - data, err := dataFetcher.GetReportersJSON() - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(reportersPanelData, panelData) { + data, err := dataFetcher.GetReportersJSON() + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.reportersResult = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.reportersResult = data + } }() go func() { defer wg.Done() - federatedServices, err := GetFederatedServices(dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } - finalSummariesDestinations, err := getFederationSummaries(federatedServices, outgoing, dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } - finalSummariesOrigins, err := getFederationSummaries(federatedServices, incoming, dataFetcher) - if err != nil { - errorSink.AppendError(err) - return - } + if shouldRetrieve(federationPanelData, panelData) { + federatedServices, err := GetFederatedServices(dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } + finalSummariesDestinations, err := getFederationSummaries(federatedServices, outgoing, dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } + finalSummariesOrigins, err := getFederationSummaries(federatedServices, incoming, dataFetcher) + if err != nil { + errorSink.AppendError(err) + return + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.federatedServices = federatedServices - result.finalSummariesDestinations = finalSummariesDestinations - result.finalSummariesOrigins = finalSummariesOrigins + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.federatedServices = federatedServices + result.finalSummariesDestinations = finalSummariesDestinations + result.finalSummariesOrigins = finalSummariesOrigins + } }() go func() { defer wg.Done() - data, err := dataFetcher.GetElasticDataDetails("flash") - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(elasticDataPanelData, panelData) { + data, err := dataFetcher.GetElasticDataDetails("flash") + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.flashResult = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.flashResult = data + } }() go func() { defer wg.Done() - data, err := dataFetcher.GetElasticDataDetails("ram") - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(elasticDataPanelData, panelData) { + data, err := dataFetcher.GetElasticDataDetails("ram") + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.ramResult = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.ramResult = data + } }() go func() { defer wg.Done() - data, err := dataFetcher.GetHTTPSessionDetailsJSON() - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(httpSessionsPanelData, panelData) { + data, err := dataFetcher.GetHTTPSessionDetailsJSON() + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.http = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.http = data + } }() go func() { defer wg.Done() - data, err := getExecutorDetails(dataFetcher, true) - if err != nil { - errorSink.AppendError(err) - } + if shouldRetrieve(executorsPanelData, panelData) { + data, err := getExecutorDetails(dataFetcher, true) + if err != nil { + errorSink.AppendError(err) + } - mutexRetrieve.Lock() - defer mutexRetrieve.Unlock() - result.executors = data + mutexRetrieve.Lock() + defer mutexRetrieve.Unlock() + result.executors = data + } }() // wait for all data fetchers requests to complete @@ -1003,3 +1048,11 @@ func retrieveClusterSummary(dataFetcher fetcher.Fetcher) (clusterSummaryInfo, [] return result, errorSink.GetErrors() } + +// shouldRetrieve returned a boolean indicating if the data for panel should be retrieved. +func shouldRetrieve(panel string, panelData []string) bool { + if len(panelData) == 0 { + return true + } + return utils.SliceContains(panelData, panel) +} diff --git a/pkg/cmd/monitor_cluster.go b/pkg/cmd/monitor_cluster.go index d185ed1..ee2983f 100644 --- a/pkg/cmd/monitor_cluster.go +++ b/pkg/cmd/monitor_cluster.go @@ -36,15 +36,16 @@ const ( var ( defaultMap = map[string]string{ - "default": "members,healthSummary:services,caches:proxies,http-servers:network-stats", - "default-service": "services:service-members,service-distributions", - "default-cache": "caches,cache-indexes:cache-access:cache-storage:cache-partitions", + "default": "members,healthSummary:services,caches:proxies,http-servers:machines,network-stats", + "default-service": "services:service-members:service-distributions", + "default-cache": "caches,cache-indexes:cache-access:cache-storage:cache-stores:cache-partitions", "default-topic": "topics:topic-members:subscribers:subscriber-groups", "default-subscriber": "topics:subscribers:subscriber-channels", } errSelectService = errors.New("you must provide a service name via -S option") - errSelectTopic = errors.New("you must select a topic using the -T option") - errSelectSubscriber = errors.New("you must select a subscriber using the -B option") + errSelectCache = errors.New("you must provide a cache using the -C option") + errSelectTopic = errors.New("you must provide a topic using the -T option") + errSelectSubscriber = errors.New("you must provide a subscriber using the -B option") mutex sync.Mutex lastClusterSummaryInfo clusterSummaryInfo emptyStringArray = make([]string, 0) @@ -64,42 +65,45 @@ var ( inHelp = false selectedCache string selectedTopic string + allBaseData []string + heightAdjust int ) var validPanels = []panelImpl{ - createContentPanel(8, "caches", "Caches", "show caches", cachesContent), - createContentPanel(8, "cache-access", "Cache Access (%SERVICE/%CACHE)", "show cache access", cacheAccessContent), - createContentPanel(8, "cache-indexes", "Cache Indexes (%SERVICE/%CACHE)", "show cache indexes", cacheIndexesContent), - createContentPanel(8, "cache-storage", "Cache Storage (%SERVICE/%CACHE)", "show cache storage", cacheStorageContent), - createContentPanel(8, "cache-partitions", "Cache Partitions (%SERVICE/%CACHE)", "show cache partitions", cachePartitionContent), - createContentPanel(8, "departedMembers", "Departed Members", "show departed members", departedMembersContent), - createContentPanel(5, "elastic-data", "Elastic Data", "show elastic data", elasticDataContent), - createContentPanel(8, "executors", "Executors", "show Executors", executorsContent), - createContentPanel(10, "healthSummary", "Health Summary", "show health summary", healthSummaryContent), - createContentPanel(5, "federation-all", "Federation All", "show all federation details", federationAllContent), - createContentPanel(5, "federation-dest", "Federation Destinations", "show federation destinations", federationDestinationsContent), - createContentPanel(5, "federation-origins", "Federation Origins", "show federation origins", federationOriginsContent), - createContentPanel(8, "http-servers", "HTTP Servers", "show HTTP servers", httpServersContent), - createContentPanel(8, "http-sessions", "HTTP Sessions", "show HTTP sessions", httpSessionsContent), - createContentPanel(5, "machines", "Machines", "show machines", machinesContent), - createContentPanel(7, "membersSummary", "Members Summary", "show members summary", membersSummaryContent), - createContentPanel(10, "members", "Members", "show members", membersContent), - createContentPanel(7, "membersShort", "Members (Short)", "show members (short)", membersOnlyContent), - createContentPanel(8, "network-stats", "Network Stats", "show network stats", networkStatsContent), - createContentPanel(8, "persistence", "Persistence", "show persistence", persistenceContent), - createContentPanel(8, "proxies", "Proxy Servers", "show proxy servers", proxiesContent), - createContentPanel(8, "proxy-connections", "Proxy Connections (%SERVICE)", "show proxy connections", proxyConnectionsContent), - createContentPanel(8, "reporters", "Reporters", "show reporters", reportersContent), - createContentPanel(8, "services", "Services", "show services", servicesContent), - createContentPanel(8, "service-members", "Service Members (%SERVICE)", "show service members", serviceMembersContent), - createContentPanel(8, "service-distributions", "Service Distributions (%SERVICE)", "show service distributions", serviceDistributionsContent), - createContentPanel(8, "service-storage", "Service Storage", "show service storage", serviceStorageContent), - createContentPanel(8, "topic-members", "Topic Members (%SERVICE/%TOPIC)", "show topic members", topicMembersContent), - createContentPanel(8, "subscribers", "Topic Subscribers (%SERVICE/%TOPIC)", "show topic subscribers", topicSubscribersContent), - createContentPanel(8, "subscriber-channels", "Subscriber Channels (%SERVICE/%TOPIC/%SUBSCRIBER)", "show topic subscriber channels", topicSubscriberChannelsContent), - createContentPanel(8, "subscriber-groups", "Subscriber Channels (%SERVICE/%TOPIC)", "show subscriber groups", topicSubscriberGroupsContent), - createContentPanel(8, "topics", "Topics", "show topics", topicsContent), - createContentPanel(8, "view-caches", "View Caches", "show view caches", viewCachesContent), + createContentPanel(8, "caches", "Caches", "show caches", cachesContent, cachesPanelData, servicesPanelData), + createContentPanel(8, "cache-access", "Cache Access (%SERVICE/%CACHE)", "show cache access", cacheAccessContent, cachesPanelData, servicesPanelData), + createContentPanel(8, "cache-indexes", "Cache Indexes (%SERVICE/%CACHE)", "show cache indexes", cacheIndexesContent, cachesPanelData, servicesPanelData), + createContentPanel(8, "cache-storage", "Cache Storage (%SERVICE/%CACHE)", "show cache storage", cacheStorageContent, cachesPanelData, servicesPanelData), + createContentPanel(8, "cache-stores", "Cache Stores (%SERVICE/%CACHE)", "show cache stores", cacheStoresContent), + createContentPanel(8, "cache-partitions", "Cache Partitions (%SERVICE/%CACHE)", "show cache partitions", cachePartitionContent, cachesPanelData, servicesPanelData), + createContentPanel(8, "departedMembers", "Departed Members", "show departed members", departedMembersContent, memberPanelData, storagePanelData), + createContentPanel(5, "elastic-data", "Elastic Data", "show elastic data", elasticDataContent, elasticDataPanelData), + createContentPanel(8, "executors", "Executors", "show Executors", executorsContent, executorsPanelData), + createContentPanel(10, "healthSummary", "Health Summary", "show health summary", healthSummaryContent, healthPanelData), + createContentPanel(5, "federation-all", "Federation All", "show all federation details", federationAllContent, federationPanelData), + createContentPanel(5, "federation-dest", "Federation Destinations", "show federation destinations", federationDestinationsContent, federationPanelData), + createContentPanel(5, "federation-origins", "Federation Origins", "show federation origins", federationOriginsContent, federationPanelData), + createContentPanel(8, "http-servers", "HTTP Servers", "show HTTP servers", httpServersContent, proxiesPanelData), + createContentPanel(8, "http-sessions", "HTTP Sessions", "show HTTP sessions", httpSessionsContent, httpSessionsPanelData), + createContentPanel(8, "machines", "Machines", "show machines", machinesContent, memberPanelData, storagePanelData), + createContentPanel(7, "membersSummary", "Members Summary", "show members summary", membersSummaryContent, memberPanelData, storagePanelData), + createContentPanel(10, "members", "Members", "show members", membersContent, memberPanelData, storagePanelData), + createContentPanel(7, "membersShort", "Members (Short)", "show members (short)", membersOnlyContent, memberPanelData, storagePanelData), + createContentPanel(8, "network-stats", "Network Stats", "show network stats", networkStatsContent, memberPanelData, storagePanelData), + createContentPanel(8, "persistence", "Persistence", "show persistence", persistenceContent, servicesPanelData), + createContentPanel(8, "proxies", "Proxy Servers", "show proxy servers", proxiesContent, proxiesPanelData), + createContentPanel(8, "proxy-connections", "Proxy Connections (%SERVICE)", "show proxy connections", proxyConnectionsContent, proxiesPanelData), + createContentPanel(8, "reporters", "Reporters", "show reporters", reportersContent, reportersPanelData), + createContentPanel(8, "services", "Services", "show services", servicesContent, servicesPanelData), + createContentPanel(8, "service-members", "Service Members (%SERVICE)", "show service members", serviceMembersContent, servicesPanelData), + createContentPanel(8, "service-distributions", "Service Distributions (%SERVICE)", "show service distributions", serviceDistributionsContent, servicesPanelData), + createContentPanel(8, "service-storage", "Service Storage", "show service storage", serviceStorageContent, servicesPanelData), + createContentPanel(8, "topic-members", "Topic Members (%SERVICE/%TOPIC)", "show topic members", topicMembersContent, topicsPanelData), + createContentPanel(8, "subscribers", "Topic Subscribers (%SERVICE/%TOPIC)", "show topic subscribers", topicSubscribersContent, topicsPanelData), + createContentPanel(8, "subscriber-channels", "Subscriber Channels (%SERVICE/%TOPIC/%SUBSCRIBER)", "show topic subscriber channels", topicSubscriberChannelsContent, topicsPanelData), + createContentPanel(8, "subscriber-groups", "Subscriber Channels (%SERVICE/%TOPIC)", "show subscriber groups", topicSubscriberGroupsContent, topicsPanelData), + createContentPanel(8, "topics", "Topics", "show topics", topicsContent, topicsPanelData), + createContentPanel(8, "view-caches", "View Caches", "show view caches", viewCachesContent, servicesPanelData), } // monitorClusterCmd represents the monitor cluster command @@ -163,6 +167,8 @@ Use --show-panels to show all available panels.`, return err } + allBaseData = getAllBaseData(parsedLayout) + // retrieve cluster details first so if we are connected // to WLS or need authentication, this can be done first _, err = dataFetcher.GetClusterDetailsJSON() @@ -280,13 +286,17 @@ func increaseMaxHeight() { for i := range validPanels { validPanels[i].MaxHeight++ } + heightAdjust++ } func decreaseMaxHeight() { - for i := range validPanels { - if validPanels[i].MaxHeight > validPanels[i].OriginalMaxHeight { - validPanels[i].MaxHeight-- + if heightAdjust > 0 { + for i := range validPanels { + if validPanels[i].MaxHeight > validPanels[i].OriginalMaxHeight { + validPanels[i].MaxHeight-- + } } + heightAdjust-- } } @@ -294,6 +304,7 @@ func resetMaxHeight() { for i := range validPanels { validPanels[i].MaxHeight = validPanels[i].OriginalMaxHeight } + heightAdjust = 0 } func refresh(screen tcell.Screen, dataFetcher fetcher.Fetcher, parsedLayout []string, refresh bool) error { @@ -357,7 +368,7 @@ func updateScreen(screen tcell.Screen, dataFetcher fetcher.Fetcher, parsedLayout screen.Show() initialRefresh = false } - lastClusterSummaryInfo, errorList = retrieveClusterSummary(dataFetcher) + lastClusterSummaryInfo, errorList = retrieveClusterSummary(dataFetcher, allBaseData...) lastDuration = time.Since(startTime) if lastDuration > time.Second { @@ -764,6 +775,35 @@ var cacheStorageContent = func(dataFetcher fetcher.Fetcher, clusterSummary clust return getCacheContent(dataFetcher, "storage") } +var cacheStoresContent = func(dataFetcher fetcher.Fetcher, clusterSummary clusterSummaryInfo) ([]string, error) { + var ( + cacheStoreResult []byte + cacheStoreDetails = config.CacheStoreDetails{} + err error + ) + + if selectedCache == "" { + return emptyStringArray, errSelectCache + } + + if serviceName, err = findServiceForCacheOrTopic(dataFetcher, selectedCache, "cache"); err != nil { + return emptyStringArray, err + } + + cacheStoreResult, err = dataFetcher.GetCacheMembers(serviceName, selectedCache) + if err != nil { + return emptyStringArray, err + } + + if err = json.Unmarshal(cacheStoreResult, &cacheStoreDetails); err != nil { + return emptyStringArray, err + } + + finalDetails := ensureTierBack(cacheStoreDetails.Details) + + return strings.Split(FormatCacheStoreDetails(finalDetails, selectedCache, serviceName, false), "\n"), nil +} + var cachePartitionContent = func(dataFetcher fetcher.Fetcher, clusterSummary clusterSummaryInfo) ([]string, error) { return getCacheContent(dataFetcher, partitionDisplayType) } @@ -782,7 +822,7 @@ func getCacheContent(dataFetcher fetcher.Fetcher, displayType string) ([]string, } if selectedCache == "" { - return emptyStringArray, errors.New("you must select a cache using the -C option") + return emptyStringArray, errSelectCache } if displayType == partitionDisplayType { @@ -961,6 +1001,7 @@ type panelImpl struct { Title string ContentFunction contentFunction Description string + BaseData []string } func (cs panelImpl) GetPanelName() string { @@ -984,7 +1025,7 @@ func (cs panelImpl) GetContentFunction() contentFunction { } // createContentPanel creates a standard content panel. -func createContentPanel(maxHeight int, panelName, title, description string, f contentFunction) panelImpl { +func createContentPanel(maxHeight int, panelName, title, description string, f contentFunction, baseData ...string) panelImpl { return panelImpl{ MaxHeight: maxHeight, OriginalMaxHeight: maxHeight, @@ -992,6 +1033,7 @@ func createContentPanel(maxHeight int, panelName, title, description string, f c Title: title, ContentFunction: f, Description: description, + BaseData: baseData, } } @@ -1037,6 +1079,25 @@ func validatePanels(layout []string) error { return nil } +func getAllBaseData(layout []string) []string { + allPanelData := make([]string, 0) + + for _, v := range layout { + // split by "," for multiple per line + s := strings.Split(v, ",") + + for _, vv := range s { + panel := getPanel(vv) + for _, b := range panel.BaseData { + if !utils.SliceContains(allPanelData, b) { + allPanelData = append(allPanelData, b) + } + } + } + } + return allPanelData +} + // drawContent draws content and returns the height it drew func drawContent(screen tcell.Screen, dataFetcher fetcher.Fetcher, panel panelImpl, x, y, w int, code rune) (int, error) { h := panel.GetMaxHeight() @@ -1045,7 +1106,7 @@ func drawContent(screen tcell.Screen, dataFetcher fetcher.Fetcher, panel panelIm content, err := panel.GetContentFunction()(dataFetcher, lastClusterSummaryInfo) if err != nil { if ignoreRESTErrors { - content = []string{" ", noContent, ""} + content = []string{" ", noContent, " "} } else { return 0, err } @@ -1128,13 +1189,25 @@ func trimBlankContent(content []string) []string { // drawHeader draws the screen header with cluster information. func drawHeader(screen tcell.Screen, w, h int, cluster config.Cluster, dataFetcher fetcher.Fetcher) { - var title string + var ( + title string + padding = " " + height = "0" + ) if cluster.ClusterName == "" && ignoreRESTErrors { title = errorContent + " from " + dataFetcher.GetURL() } else { version := strings.Split(cluster.Version, " ") - title = fmt.Sprintf("Coherence CLI: %s - Monitoring cluster %s (%s) ESC to quit %s. (%v)", - time.Now().Format(time.DateTime), cluster.ClusterName, version[0], additionalMonitorMsg, lastDuration) + if padMaxHeightParam { + padding = "P" + } + if heightAdjust <= 0 { + height = " 0 " + } else if heightAdjust > 0 { + height = fmt.Sprintf("+%v ", heightAdjust) + } + title = fmt.Sprintf("Coherence CLI: %s - Monitoring cluster %s (%s) ESC to quit %s. %s%s(%v)", + time.Now().Format(time.DateTime), cluster.ClusterName, version[0], additionalMonitorMsg, padding, height, lastDuration) } drawText(screen, 1, 0, w-1, h-1, tcell.StyleDefault.Reverse(true), title) } diff --git a/scripts/monitor.expect b/scripts/monitor.expect new file mode 100644 index 0000000..1fff243 --- /dev/null +++ b/scripts/monitor.expect @@ -0,0 +1,19 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2024, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# +set env(TERM) vt100 +set timeout 10 +spawn $env(DIR)/bin/cohctl --config-dir $env(CONFIG_DIR) monitor cluster local +expect { + "Coherence CLI" { + sleep 10 + send \003 + } + timeout { + send_user "Failed to receive expected output: 'Coherence CLI'\n" + exit 1 + } + } \ No newline at end of file diff --git a/scripts/test-monitor-cluster.sh b/scripts/test-monitor-cluster.sh new file mode 100755 index 0000000..5f2ef89 --- /dev/null +++ b/scripts/test-monitor-cluster.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# + +# Test monitor cluster + +pwd + +if [ $# -ne 1 ] ; then + echo "Usage: $0 Coherence-Version" + exit +fi + +VERSION=$1 + +export CONFIG_DIR=/tmp/$$.create +export DIR=`pwd` +OUTPUT=/tmp/$$.output + +mkdir -p ${CONFIG_DIR} +trap "cp ${CONFIG_DIR}/cohctl.log /tmp && rm -rf ${CONFIG_DIR} $OUTPUT" 0 1 2 3 + +echo +echo "Config Dir: ${CONFIG_DIR}" +echo "Version: ${VERSION}" +echo "Commercial: ${COM}" +echo + +# Build the Java project so we get any deps downloaded + +COHERENCE_GROUP_ID=com.oracle.coherence.ce +if [ ! -z "$COM" ] ; then + COHERENCE_GROUP_ID=com.oracle.coherence +fi + +mvn -f java/coherence-cli-test dependency:build-classpath -Dcoherence.group.id=${COHERENCE_GROUP_ID} -Dcoherence.version=${VERSION} + +# Default command +COHCTL="$DIR/bin/cohctl --config-dir ${CONFIG_DIR}" + +function pause() { + echo "sleeping..." + sleep 5 +} + +function wait_for_ready() { + counter=10 + PORT=$1 + if [ -z "$PORT" ] ; then + PORT=30000 + fi + pause + echo "waiting for management to be ready on $PORT ..." + while [ $counter -gt 0 ] + do + curl http://127.0.0.1:${PORT}/management/coherence/cluster > /dev/null 2>&1 + ret=$? + if [ $ret -eq 0 ] ; then + echo "Management ready" + pause + return 0 + fi + pause + let counter=counter-1 + done + echo "Management failed to be ready" + save_logs + exit 1 +} + +function message() { + echo "=========================================================" + echo "$*" +} + +function save_logs() { + mkdir -p build/_output/test-logs + cp ${CONFIG_DIR}/logs/local/*.log build/_output/test-logs || true +} + +function runCommand() { + echo "=========================================================" + echo "Running command: cohctl $*" + $COHCTL $* > $OUTPUT 2>&1 + ret=$? + cat $OUTPUT + if [ $ret -ne 0 ] ; then + echo "Command failed" + # copy the log files + save_logs + exit 1 + fi +} + +runCommand version +runCommand set debug on + +# Create a cluster +message "Create Cluster" +runCommand create cluster local -y -v $VERSION $COM -S com.tangosol.net.Coherence + +wait_for_ready + +expect -f $DIR/scripts/monitor.expect +ret=$? + +# Shutdown +runCommand stop cluster local -y +runCommand remove cluster local -y + +exit $ret