Esempio n. 1
0
        public bool DetermineFailure()
        {
            Utilities.WriteDebug($"Determining failure for {Repo}...", Log, LogLevel);
            if (BuildBreakdowns.Count == 0)
            {
                Utilities.WriteDebug($"No builds found for {Repo} this rollout; rollout marked as FAILED.", Log, LogLevel);
                return(true);
            }

            ScorecardBuildBreakdown lastBuild = BuildBreakdowns.Last();

            Utilities.WriteDebug($"Last build is for {Repo} is {lastBuild.BuildSummary.BuildNumber} ({lastBuild.BuildSummary.WebLink})", Log, LogLevel);

            if (lastBuild.Score.Rollbacks == 1)
            {
                Utilities.WriteDebug($"Last build ({lastBuild.BuildSummary.BuildNumber}) was a rollback; rollout marked as FAILED.", Log, LogLevel);
                return(true);
            }

            string lastBuildResult = lastBuild.BuildSummary.Result;

            Utilities.WriteDebug($"Build {lastBuild.BuildSummary.BuildNumber} has result '{lastBuildResult}'", Log, LogLevel);
            switch (lastBuildResult)
            {
            case "succeeded":
            case "partiallySucceeded":
                Utilities.WriteDebug($"Last build determined successful.", Log, LogLevel);
                return(false);

            default:
                Utilities.WriteDebug($"Last build determined unsuccessful; rollout marked as FAILED.", Log, LogLevel);
                return(true);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Parses a Scorecard object from a given CSV file
        /// </summary>
        /// <param name="filePath">Path to CSV file</param>
        /// <param name="config">Config object to use during parsing</param>
        /// <returns></returns>
        public async static Task <Scorecard> ParseScorecardFromCsvAsync(string filePath, Config config)
        {
            using (StreamReader file = new StreamReader(filePath))
            {
                Scorecard scorecard = new Scorecard
                {
                    RolloutWeightConfig = config.RolloutWeightConfig
                };

                string[] rolloutInfo = (await file.ReadLineAsync()).Split(',');
                scorecard.Repo = config.RepoConfigs.Find(r => r.Repo == rolloutInfo[0]);
                scorecard.Date = DateTimeOffset.Parse(rolloutInfo[1]);
                await file.ReadLineAsync();

                await file.ReadLineAsync();

                string[] rolloutScorecardSummary = (await file.ReadLineAsync()).Split(',');
                scorecard.TimeToRollout  = TimeSpan.Parse(rolloutScorecardSummary[0]);
                scorecard.CriticalIssues = int.Parse(rolloutScorecardSummary[1]);
                scorecard._githubIssuesUris.AddRange(GetIssueLinksFromString(rolloutScorecardSummary[2]));
                scorecard.Hotfixes = int.Parse(rolloutScorecardSummary[3]);
                scorecard._githubIssuesUris.AddRange(GetIssueLinksFromString(rolloutScorecardSummary[4]));
                scorecard.Rollbacks = int.Parse(rolloutScorecardSummary[5]);
                scorecard._githubIssuesUris.AddRange(GetIssueLinksFromString(rolloutScorecardSummary[6]));
                scorecard.Downtime = TimeSpan.Parse(rolloutScorecardSummary[7]);
                scorecard.Failure  = bool.Parse(rolloutScorecardSummary[8]);
                scorecard._githubIssuesUris.AddRange(GetIssueLinksFromString(rolloutScorecardSummary[9]));
                await file.ReadLineAsync();

                await file.ReadLineAsync();

                string[] buildBreakdownLines = (await file.ReadToEndAsync()).Split('\n');
                foreach (string breakdownLine in buildBreakdownLines)
                {
                    if (breakdownLine.Length == 0)
                    {
                        break;
                    }

                    BuildSummary buildSummary = new BuildSummary();

                    string[] breakdownSummary = breakdownLine.Split(',');
                    buildSummary.BuildNumber        = breakdownSummary[0];
                    buildSummary.Links.WebLink.Href = breakdownSummary[1];

                    ScorecardBuildBreakdown buildBreakdown = new ScorecardBuildBreakdown(buildSummary);

                    buildBreakdown.Score.TimeToRollout  = TimeSpan.Parse(breakdownSummary[2]);
                    buildBreakdown.Score.CriticalIssues = int.Parse(breakdownSummary[3]);
                    buildBreakdown.Score.Hotfixes       = int.Parse(breakdownSummary[4]);
                    buildBreakdown.Score.Rollbacks      = int.Parse(breakdownSummary[5]);
                    buildBreakdown.Score.Downtime       = TimeSpan.Parse(breakdownSummary[6]);

                    scorecard.BuildBreakdowns.Add(buildBreakdown);
                }

                return(scorecard);
            }
        }
Esempio n. 3
0
        public async Task CollectStages(ScorecardBuildBreakdown buildBreakdown)
        {
            string timelineLinkWithAttempts = $"{buildBreakdown.BuildSummary.TimelineLink}?changeId=1";

            Utilities.WriteDebug($"Querying AzDO API: {timelineLinkWithAttempts}", Log, LogLevel);
            JObject jsonTimelineResponse = await GetAzdoApiResponseAsync(timelineLinkWithAttempts);

            BuildTimeline timeline = jsonTimelineResponse.ToObject <BuildTimeline>();

            if (timeline.Records != null)
            {
                // We're going to use this to store previous attempts as we find them
                Dictionary <string, BuildTimeline> previousAttemptTimelines = new Dictionary <string, BuildTimeline>();

                foreach (BuildTimelineEntry record in timeline.Records)
                {
                    // We measure times at the stage level because this is the simplest thing to do
                    // By taking the min start time and max end time of all the stages except for the ones we exclude,
                    // we can determine a pretty accurate measurement of how long it took to rollout
                    if ((record.Type == "Checkpoint.Approval" || record.Type == "Stage") && !RepoConfig.ExcludeStages.Any(s => s == record.Name))
                    {
                        buildBreakdown.BuildSummary.Stages.Add(record);

                        if (record.PreviousAttempts.Count > 0)
                        {
                            // we're going to just add these attempts as additional stages
                            foreach (PreviousAttempt attempt in record.PreviousAttempts)
                            {
                                if (!previousAttemptTimelines.ContainsKey(attempt.TimelineId))
                                {
                                    previousAttemptTimelines.Add(attempt.TimelineId,
                                                                 (await GetAzdoApiResponseAsync($"{buildBreakdown.BuildSummary.TimelineLink}/{attempt.TimelineId}")).ToObject <BuildTimeline>());
                                }

                                if (previousAttemptTimelines[attempt.TimelineId] != null)
                                {
                                    buildBreakdown.BuildSummary.Stages.Add(previousAttemptTimelines[attempt.TimelineId].Records
                                                                           .Where(t => (t.Type == "Checkpoint.Approval" || t.Type == "Stage") && t.Name == record.Name).First());
                                }
                            }
                        }
                    }
                }

                if (buildBreakdown.BuildSummary.Stages.Any(s => s.Name.StartsWith("deploy", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(s.EndTime)))
                {
                    buildBreakdown.BuildSummary.DeploymentReached = true;
                    Utilities.WriteDebug($"Build {buildBreakdown.BuildSummary.BuildNumber} determined to have reached deployment.", Log, LogLevel);
                }
                else
                {
                    Utilities.WriteDebug($"Build {buildBreakdown.BuildSummary.BuildNumber} determined to NOT have reached deployment.", Log, LogLevel);
                }
            }
        }
Esempio n. 4
0
        public async static Task <Scorecard> CreateScorecardAsync(RolloutScorer rolloutScorer)
        {
            (int numHotfixes, int numRollbacks) = await rolloutScorer.CalculateNumHotfixesAndRollbacksFromAzdoAsync();

            List <Issue> githubIssues = await rolloutScorer.GetRolloutIssuesFromGithubAsync();

            string    repoLabel = rolloutScorer.RepoConfig.GithubIssueLabel;
            Scorecard scorecard = new Scorecard
            {
                Repo           = rolloutScorer.RepoConfig,
                Date           = rolloutScorer.RolloutStartDate,
                TimeToRollout  = await rolloutScorer.CalculateTimeToRolloutAsync(),
                CriticalIssues = githubIssues
                                 .Count(issue => Utilities.IssueContainsRelevantLabels(issue, GithubLabelNames.IssueLabel, repoLabel, rolloutScorer.Log)),
                Hotfixes = numHotfixes + githubIssues
                           .Count(issue => Utilities.IssueContainsRelevantLabels(issue, GithubLabelNames.HotfixLabel, repoLabel, rolloutScorer.Log)),
                Rollbacks = numRollbacks + githubIssues
                            .Count(issue => Utilities.IssueContainsRelevantLabels(issue, GithubLabelNames.RollbackLabel, repoLabel, rolloutScorer.Log)),
                Downtime            = await rolloutScorer.CalculateDowntimeAsync(githubIssues) + rolloutScorer.Downtime,
                Failure             = rolloutScorer.DetermineFailure() || rolloutScorer.Failed,
                BuildBreakdowns     = rolloutScorer.BuildBreakdowns,
                RolloutWeightConfig = rolloutScorer.RolloutWeightConfig,
                GithubIssues        = githubIssues,
            };

            // Critical issues and manual hotfixes/rollbacks need to be included in the build breakdowns, but it isn't possible to determine which
            // builds they belong to; so we'll just append the issues to the first build and the hotfixes/rollbacks to the last
            if (scorecard.BuildBreakdowns.Count > 0)
            {
                scorecard.BuildBreakdowns.Sort((x, y) => x.BuildSummary.BuildNumber.CompareTo(y.BuildSummary.BuildNumber));

                // Critical issues are assumed to have been caused by the first deployment
                ScorecardBuildBreakdown firstDeployment = scorecard.BuildBreakdowns.First();
                firstDeployment.Score.CriticalIssues += scorecard.CriticalIssues;
                firstDeployment.Score.GithubIssues.AddRange(scorecard.GithubIssues
                                                            .Where(issue => Utilities.IssueContainsRelevantLabels(issue, GithubLabelNames.IssueLabel, repoLabel, rolloutScorer.Log)));

                // Hotfixes & rollbacks are assumed to have taken place in the last deployment
                // This is likely incorrect given >2 deployments but can be manually adjusted if necessary
                ScorecardBuildBreakdown lastDeployment = scorecard.BuildBreakdowns.Last();
                lastDeployment.Score.Hotfixes  += rolloutScorer.ManualHotfixes;
                lastDeployment.Score.Rollbacks += rolloutScorer.ManualRollbacks;
                lastDeployment.Score.GithubIssues.AddRange(scorecard.GithubIssues
                                                           .Where(issue =>
                                                                  Utilities.IssueContainsRelevantLabels(issue, GithubLabelNames.HotfixLabel, repoLabel, rolloutScorer.Log) ||
                                                                  Utilities.IssueContainsRelevantLabels(issue, GithubLabelNames.RollbackLabel, repoLabel, rolloutScorer.Log)));
            }

            return(scorecard);
        }
        public bool DetermineFailure(List <Issue> githubIssues)
        {
            Utilities.WriteDebug($"Determining failure for {Repo}...", Log, LogLevel);
            if (githubIssues.Any(i => Utilities.IssueContainsRelevantLabels(i, GithubLabelNames.FailureLabel, RepoConfig.GithubIssueLabel, Log, LogLevel)))
            {
                Utilities.WriteDebug($"Issue with failure tag found for {Repo}; rollout marked as FAILED", Log, LogLevel);
                return(true);
            }

            if (BuildBreakdowns.Count == 0)
            {
                Utilities.WriteDebug($"No builds found for {Repo} this rollout; rollout marked as FAILED.", Log, LogLevel);
                return(true);
            }

            ScorecardBuildBreakdown lastBuild = BuildBreakdowns.Last();

            Utilities.WriteDebug($"Last build is for {Repo} is {lastBuild.BuildSummary.BuildNumber} ({lastBuild.BuildSummary.WebLink})", Log, LogLevel);

            if (lastBuild.Score.Rollbacks == 1)
            {
                Utilities.WriteDebug($"Last build ({lastBuild.BuildSummary.BuildNumber}) was a rollback; rollout marked as FAILED.", Log, LogLevel);
                return(true);
            }

            string lastBuildResult = lastBuild.BuildSummary.Result;

            Utilities.WriteDebug($"Build {lastBuild.BuildSummary.BuildNumber} has result '{lastBuildResult}'", Log, LogLevel);
            switch (lastBuildResult)
            {
            case "succeeded":
            case "partiallySucceeded":
                Utilities.WriteDebug($"Last build determined successful.", Log, LogLevel);
                return(false);

            default:
                Utilities.WriteDebug($"Last build determined unsuccessful; rollout marked as FAILED.", Log, LogLevel);
                return(true);
            }
        }