<commit_before>//  Copyright 2017 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

package daisy

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"
)

const (
	defaultInterval = "10s"
)

// WaitForInstancesSignal is a Daisy WaitForInstancesSignal workflow step.
type WaitForInstancesSignal []*InstanceSignal

// SerialOutput describes text signal strings that will be written to the serial
// port.
// A StatusMatch will print out the matching line from the StatusMatch onward.
// This step will not complete until a line in the serial output matches
// SuccessMatch or FailureMatch. A match with FailureMatch will cause the step
// to fail.
type SerialOutput struct {
	Port         int64
	SuccessMatch string
	FailureMatch string
	StatusMatch  string
}

// InstanceSignal waits for a signal from an instance.
type InstanceSignal struct {
	// Instance name to wait for.
	Name string
	// Interval to check for signal (default is 5s).
	// Must be parsable by https://golang.org/pkg/time/#ParseDuration.
	Interval string
	interval time.Duration
	// Wait for the instance to stop.
	Stopped bool
	// Wait for a string match in the serial output.
	SerialOutput *SerialOutput
}

func waitForInstanceStopped(w *Workflow, project, zone, name string, interval time.Duration) error {
	w.logger.Printf("WaitForInstancesSignal: waiting for instance %q to stop.", name)
	tick := time.Tick(interval)
	for {
		select {
		case <-w.Cancel:
			return nil
		case <-tick:
			stopped, err := w.ComputeClient.InstanceStopped(project, zone, name)
			if err != nil {
				return err
			}
			if stopped {
				w.logger.Printf("WaitForInstancesSignal: instance %q stopped.", name)
				return nil
			}
		}
	}
}

func waitForSerialOutput(w *Workflow, project, zone, name string, so *SerialOutput, interval time.Duration) error {
	msg := fmt.Sprintf("WaitForInstancesSignal: watching serial port %d", so.Port)
	if so.SuccessMatch != "" {
		msg += fmt.Sprintf(", SuccessMatch: %q", so.SuccessMatch)
	}
	if so.FailureMatch != "" {
		msg += fmt.Sprintf(", FailureMatch: %q", so.FailureMatch)
	}
	if so.StatusMatch != "" {
		msg += fmt.Sprintf(", StatusMatch: %q", so.StatusMatch)
	}
	w.logger.Print(msg + ".")
	var start int64
	var errs int
	tick := time.Tick(interval)
	for {
		select {
		case <-w.Cancel:
			return nil
		case <-tick:
			resp, err := w.ComputeClient.GetSerialPortOutput(project, zone, name, so.Port, start)
			if err != nil {
				status, sErr := w.ComputeClient.InstanceStatus(project, zone, name)
				if sErr == nil && (status == "TERMINATED" || status == "STOPPING" || status == "STOPPED") {
					w.logger.Printf("WaitForInstancesSignal: instance %q stopped, not waiting for serial output.", name)
					return nil
				}
				// Retry up to 3 times in a row on any error if we successfully got InstanceStatus.
				if sErr == nil && errs < 3 {
					errs++
					continue
				}
				if sErr != nil {
					err = fmt.Errorf("%v, error geting InstanceStatus: %v", err, sErr)
				} else {
					err = fmt.Errorf("%v, InstanceStatus: %q", err, status)
				}
				return fmt.Errorf("WaitForInstancesSignal: instance %q: error getting serial port: %v", name, err)
			}
			start = resp.Next
			for _, ln := range strings.Split(resp.Contents, "\n") {
				if so.StatusMatch != "" {
					if i := strings.Index(ln, so.StatusMatch); i != -1 {
						w.logger.Printf("WaitForInstancesSignal: StatusMatch found for %q: %q", name, strings.TrimSpace(ln[i:]))
					}
				}
				if so.FailureMatch != "" {
					if i := strings.Index(ln, so.FailureMatch); i != -1 {
						return fmt.Errorf("WaitForInstancesSignal: FailureMatch found for %q: %q", name, strings.TrimSpace(ln[i:]))
					}
				}
				if so.SuccessMatch != "" {
					if i := strings.Index(ln, so.SuccessMatch); i != -1 {
						w.logger.Printf("WaitForInstancesSignal: SuccessMatch found for %q: %q", name, strings.TrimSpace(ln[i:]))
						return nil
					}
				}
			}
			errs = 0<commit_msg>Don't return from waitForSerialOutput if instance is STOPPING (#188)<commit_after>//  Copyright 2017 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

package daisy

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"
)

const (
	defaultInterval = "10s"
)

// WaitForInstancesSignal is a Daisy WaitForInstancesSignal workflow step.
type WaitForInstancesSignal []*InstanceSignal

// SerialOutput describes text signal strings that will be written to the serial
// port.
// A StatusMatch will print out the matching line from the StatusMatch onward.
// This step will not complete until a line in the serial output matches
// SuccessMatch or FailureMatch. A match with FailureMatch will cause the step
// to fail.
type SerialOutput struct {
	Port         int64
	SuccessMatch string
	FailureMatch string
	StatusMatch  string
}

// InstanceSignal waits for a signal from an instance.
type InstanceSignal struct {
	// Instance name to wait for.
	Name string
	// Interval to check for signal (default is 5s).
	// Must be parsable by https://golang.org/pkg/time/#ParseDuration.
	Interval string
	interval time.Duration
	// Wait for the instance to stop.
	Stopped bool
	// Wait for a string match in the serial output.
	SerialOutput *SerialOutput
}

func waitForInstanceStopped(w *Workflow, project, zone, name string, interval time.Duration) error {
	w.logger.Printf("WaitForInstancesSignal: waiting for instance %q to stop.", name)
	tick := time.Tick(interval)
	for {
		select {
		case <-w.Cancel:
			return nil
		case <-tick:
			stopped, err := w.ComputeClient.InstanceStopped(project, zone, name)
			if err != nil {
				return err
			}
			if stopped {
				w.logger.Printf("WaitForInstancesSignal: instance %q stopped.", name)
				return nil
			}
		}
	}
}

func waitForSerialOutput(w *Workflow, project, zone, name string, so *SerialOutput, interval time.Duration) error {
	msg := fmt.Sprintf("WaitForInstancesSignal: watching serial port %d", so.Port)
	if so.SuccessMatch != "" {
		msg += fmt.Sprintf(", SuccessMatch: %q", so.SuccessMatch)
	}
	if so.FailureMatch != "" {
		msg += fmt.Sprintf(", FailureMatch: %q", so.FailureMatch)
	}
	if so.StatusMatch != "" {
		msg += fmt.Sprintf(", StatusMatch: %q", so.StatusMatch)
	}
	w.logger.Print(msg + ".")
	var start int64
	var errs int
	tick := time.Tick(interval)
	for {
		select {
		case <-w.Cancel:
			return nil
		case <-tick:
			resp, err := w.ComputeClient.GetSerialPortOutput(project, zone, name, so.Port, start)
			if err != nil {
				status, sErr := w.ComputeClient.InstanceStatus(project, zone, name)
				if sErr != nil {
					err = fmt.Errorf("%v, error geting InstanceStatus: %v", err, sErr)
				} else {
					err = fmt.Errorf("%v, InstanceStatus: %q", err, status)
				}

				if status == "TERMINATED" || status == "STOPPED" {
					w.logger.Printf("WaitForInstancesSignal: instance %q stopped, not waiting for serial output.", name)
					return nil
				}
				// Keep retrying until the instance is STOPPED.
				if status == "STOPPING" {
					continue
				}
				// Retry up to 3 times in a row on any error if we successfully got InstanceStatus.
				if errs < 3 {
					errs++
					continue
				}

				return fmt.Errorf("WaitForInstancesSignal: instance %q: error getting serial port: %v", name, err)
			}
			start = resp.Next
			for _, ln := range strings.Split(resp.Contents, "\n") {
				if so.StatusMatch != "" {
					if i := strings.Index(ln, so.StatusMatch); i != -1 {
						w.logger.Printf("WaitForInstancesSignal: StatusMatch found for %q: %q", name, strings.TrimSpace(ln[i:]))
					}
				}
				if so.FailureMatch != "" {
					if i := strings.Index(ln, so.FailureMatch); i != -1 {
						return fmt.Errorf("WaitForInstancesSignal: FailureMatch found for %q: %q", name, strings.TrimSpace(ln[i:]))
					}
				}
				if so.SuccessMatch != "" {
					if i := strings.Index(ln, so.SuccessMatch); i != -1 {
						w.logger.Printf("WaitForInstancesSignal: SuccessMatch found for %q: %q", name, strings.TrimSpace(ln[i:]))
						return nil
					}
				}
			}
			errs = 0
{'fraction_non_alphanumeric': 0.11770568043406046, 'fraction_numerical': 0.004982836895138966, 'mean_word_length': 3.260377358490566, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3817942', 'n_tokens_mistral': 3148, 'n_tokens_neox': 2769, 'n_words': 962}