github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/api/v1/state.go (about)

     1  /*
     2  Copyright 2021 The TestGrid Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"math"
    24  	"net/http"
    25  
    26  	"github.com/go-chi/chi"
    27  	"github.com/golang/protobuf/ptypes/timestamp"
    28  	"github.com/sirupsen/logrus"
    29  
    30  	"github.com/GoogleCloudPlatform/testgrid/config"
    31  	apipb "github.com/GoogleCloudPlatform/testgrid/pb/api/v1"
    32  	statepb "github.com/GoogleCloudPlatform/testgrid/pb/state"
    33  	"github.com/GoogleCloudPlatform/testgrid/pkg/tabulator"
    34  	"github.com/GoogleCloudPlatform/testgrid/util/gcs"
    35  )
    36  
    37  // findDashboardTab locates dashboard tab in config, given a dashboard and tab name.
    38  func findDashboardTab(cfg *cachedConfig, dashboardInput string, tabInput string) (string, string, string, error) {
    39  	if cfg == nil || cfg.Config == nil {
    40  		return "", "", "", errors.New("empty config")
    41  	}
    42  
    43  	dashboardKey := config.Normalize(dashboardInput)
    44  	tabKey := config.Normalize(tabInput)
    45  
    46  	dashboardName, ok := cfg.NormalDashboard[dashboardKey]
    47  	if !ok {
    48  		return "", "", "", fmt.Errorf("Dashboard {%q} not found", dashboardKey)
    49  	}
    50  	tabName, ok := cfg.NormalDashboardTab[dashboardKey][tabKey]
    51  	if !ok {
    52  		return dashboardName, "", "", fmt.Errorf("Tab {%q} not found", tabKey)
    53  	}
    54  
    55  	for _, tab := range cfg.Config.Dashboards[dashboardName].DashboardTab {
    56  		if tab.Name == tabName {
    57  			return dashboardName, tabName, tab.TestGroupName, nil
    58  		}
    59  	}
    60  
    61  	return dashboardName, tabName, "", fmt.Errorf("Test group not found")
    62  }
    63  
    64  // Grid fetch tab and grid info (columns, rows, ..etc)
    65  func (s Server) Grid(ctx context.Context, scope string, dashboardName, tabName, testGroupNanme string) (*statepb.Grid, error) {
    66  	configPath, _, err := s.configPath(scope)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	path, err := tabulator.TabStatePath(*configPath, s.TabPathPrefix, dashboardName, tabName)
    71  	if err != nil {
    72  		return nil, fmt.Errorf("tab state path: %v", err)
    73  	}
    74  	grid, _, err := gcs.DownloadGrid(ctx, s.Client, *path)
    75  	return grid, err
    76  }
    77  
    78  // decodeRLE decodes the run length encoded data
    79  //
    80  //	[0, 3, 5, 4] -> [0, 0, 0, 5, 5, 5, 5]
    81  func decodeRLE(encodedData []int32) []int32 {
    82  	var decodedResult []int32
    83  	encodedDataLength := len(encodedData)
    84  	if encodedDataLength%2 == 0 {
    85  		for encodedDataIdx := 0; encodedDataIdx < encodedDataLength; encodedDataIdx += 2 {
    86  			for cellRepeatCount := encodedData[encodedDataIdx+1]; cellRepeatCount > 0; cellRepeatCount-- {
    87  				decodedResult = append(decodedResult, encodedData[encodedDataIdx])
    88  			}
    89  		}
    90  	}
    91  	return decodedResult
    92  }
    93  
    94  // ListHeaders returns dashboard tab headers
    95  func (s *Server) ListHeaders(ctx context.Context, req *apipb.ListHeadersRequest) (*apipb.ListHeadersResponse, error) {
    96  	ctx, cancel := context.WithTimeout(ctx, s.Timeout)
    97  	defer cancel()
    98  
    99  	cfg, err := s.getConfig(ctx, logrus.WithContext(ctx), req.GetScope())
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	cfg.Mutex.RLock()
   104  	defer cfg.Mutex.RUnlock()
   105  
   106  	dashboardName, tabName, testGroupName, err := findDashboardTab(cfg, req.GetDashboard(), req.GetTab())
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	grid, err := s.Grid(ctx, req.GetScope(), dashboardName, tabName, testGroupName)
   112  	if err != nil {
   113  		return nil, fmt.Errorf("Dashboard {%q} or tab {%q} not found", req.GetDashboard(), req.GetTab())
   114  	}
   115  	if grid == nil {
   116  		return nil, errors.New("grid not found")
   117  	}
   118  
   119  	var dashboardTabResponse apipb.ListHeadersResponse
   120  	for _, gColumn := range grid.Columns {
   121  		// TODO(#683): Remove timestamp conversion math
   122  		millis := gColumn.Started
   123  		sec := millis / 1000
   124  		nanos := math.Mod(millis, 1000) * 1e6
   125  		column := apipb.ListHeadersResponse_Header{
   126  			Name:  gColumn.Name,
   127  			Build: gColumn.Build,
   128  			Started: &timestamp.Timestamp{
   129  				Seconds: int64(sec),
   130  				Nanos:   int32(nanos),
   131  			},
   132  			Extra:      gColumn.Extra,
   133  			HotlistIds: gColumn.HotlistIds,
   134  		}
   135  		dashboardTabResponse.Headers = append(dashboardTabResponse.Headers, &column)
   136  	}
   137  	return &dashboardTabResponse, nil
   138  }
   139  
   140  // ListHeadersHTTP returns dashboard tab headers
   141  // Response json: ListHeadersResponse
   142  func (s Server) ListHeadersHTTP(w http.ResponseWriter, r *http.Request) {
   143  	req := apipb.ListHeadersRequest{
   144  		Scope:     r.URL.Query().Get(scopeParam),
   145  		Dashboard: chi.URLParam(r, "dashboard"),
   146  		Tab:       chi.URLParam(r, "tab"),
   147  	}
   148  	resp, err := s.ListHeaders(r.Context(), &req)
   149  	if err != nil {
   150  		http.Error(w, err.Error(), http.StatusNotFound)
   151  		return
   152  	}
   153  
   154  	s.writeJSON(w, resp)
   155  }
   156  
   157  // ListRows returns dashboard tab rows
   158  func (s *Server) ListRows(ctx context.Context, req *apipb.ListRowsRequest) (*apipb.ListRowsResponse, error) {
   159  	ctx, cancel := context.WithTimeout(ctx, s.Timeout)
   160  	defer cancel()
   161  
   162  	// this should be factored out of this function
   163  	cfg, err := s.getConfig(ctx, logrus.WithContext(ctx), req.GetScope())
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	cfg.Mutex.RLock()
   168  	defer cfg.Mutex.RUnlock()
   169  
   170  	dashboardName, tabName, testGroupName, err := findDashboardTab(cfg, req.GetDashboard(), req.GetTab())
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	grid, err := s.Grid(ctx, req.GetScope(), dashboardName, tabName, testGroupName)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("Dashboard {%q} or tab {%q} not found", req.GetDashboard(), req.GetTab())
   178  	}
   179  	if grid == nil {
   180  		return nil, errors.New("grid not found")
   181  	}
   182  
   183  	dashboardTabResponse := apipb.ListRowsResponse{
   184  		Rows: make([]*apipb.ListRowsResponse_Row, 0, len(grid.Rows)),
   185  	}
   186  	for _, gRow := range grid.Rows {
   187  		gRowDecodedResults := decodeRLE(gRow.Results)
   188  		cellsCount := len(gRowDecodedResults)
   189  		row := apipb.ListRowsResponse_Row{
   190  			Name:   gRow.Name,
   191  			Issues: gRow.Issues,
   192  			Alert:  gRow.AlertInfo,
   193  			Cells:  make([]*apipb.ListRowsResponse_Cell, 0, cellsCount),
   194  		}
   195  		var filledIdx int
   196  		// loop through CellIds, Messages, Icons slices and build cell struct objects
   197  		for cellIdx := 0; cellIdx < cellsCount; cellIdx++ {
   198  			// Cell IDs, messages, and icons are only listed for non-blank cells.
   199  			cell := apipb.ListRowsResponse_Cell{
   200  				Result: gRowDecodedResults[cellIdx],
   201  			}
   202  			if gRowDecodedResults[cellIdx] != 0 {
   203  				// Cell IDs may be omitted for subrows.
   204  				if len(gRow.CellIds) != 0 {
   205  					cell.CellId = gRow.CellIds[filledIdx]
   206  				}
   207  				if len(gRow.Messages) != 0 {
   208  					cell.Message = gRow.Messages[filledIdx]
   209  				}
   210  				if len(gRow.Icons) != 0 {
   211  					cell.Icon = gRow.Icons[filledIdx]
   212  				}
   213  				filledIdx++
   214  			}
   215  			row.Cells = append(row.Cells, &cell)
   216  		}
   217  		dashboardTabResponse.Rows = append(dashboardTabResponse.Rows, &row)
   218  	}
   219  	return &dashboardTabResponse, nil
   220  }
   221  
   222  // ListRowsHTTP returns dashboard tab rows
   223  // Response json: ListRowsResponse
   224  func (s Server) ListRowsHTTP(w http.ResponseWriter, r *http.Request) {
   225  	req := apipb.ListRowsRequest{
   226  		Scope:     r.URL.Query().Get(scopeParam),
   227  		Dashboard: chi.URLParam(r, "dashboard"),
   228  		Tab:       chi.URLParam(r, "tab"),
   229  	}
   230  	resp, err := s.ListRows(r.Context(), &req)
   231  	if err != nil {
   232  		http.Error(w, err.Error(), http.StatusNotFound)
   233  		return
   234  	}
   235  
   236  	s.writeJSON(w, resp)
   237  }