<commit_before>// Copyright 2018 the Service Broker Project Authors.
//
// 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 client

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/broker"
	"github.com/pivotal-cf/brokerapi"
)

// RunExamplesForService runs all the exmaples for a given service name against
// the service broker pointed to by client. All examples in the registry get run
// if serviceName is blank. If exampleName is non-blank then only the example
// with the given name is run.
func RunExamplesForService(registry broker.BrokerRegistry, client *Client, serviceName, exampleName string) error {
	rand.Seed(time.Now().UTC().UnixNano())

	services := registry.GetAllServices()

	for _, service := range services {
		if serviceName != "" && serviceName != service.Name {
			continue
		}

		for _, example := range service.Examples {
			if exampleName != "" && example.Name != exampleName {
				continue
			}

			if err := RunExample(client, example, service); err != nil {
				return err
			}
		}
	}

	return nil

}

// RunExample runs a single example against the given service on the broker
// pointed to by client.
func RunExample(client *Client, example broker.ServiceExample, service *broker.ServiceDefinition) error {
	executor, err := newExampleExecutor(client, example, service)
	if err != nil {
		return err
	}

	executor.LogTestInfo()

	// Cleanup the test if it fails partway through
	defer func() {
		log.Println("Cleaning up the environment")
		executor.Unbind()
		executor.Deprovision()
	}()

	if err := executor.Provision(); err != nil {
		return err
	}

	bindResponse, err := executor.Bind()
	if err != nil {
		return err
	}

	if err := executor.Unbind(); err != nil {
		return err
	}

	if err := executor.Deprovision(); err != nil {
		return err
	}

	// Check that the binding response has the same fields as expected
	var binding brokerapi.Binding
	err = json.Unmarshal(bindResponse, &binding)
	if err != nil {
		return err
	}

	credentialsEntry := binding.Credentials.(map[string]interface{})
	if err := broker.ValidateVariables(credentialsEntry, service.BindOutputVariables); err != nil {
		log.Printf("Error: results don't match JSON Schema: %v", err)
		return err
	}

	return nil
}

func retry(timeout, period time.Duration, function func() (tryAgain bool, err error)) error {
	to := time.After(timeout)
	tick := time.Tick(period)

	if tryAgain, err := function(); !tryAgain {
		return err
	}

	// Keep trying until we're timed out or got a result or got an error
	for {
		select {
		case <-to:
			return errors.New("Timeout while waiting for result")
		case <-tick:
			tryAgain, err := function()

			if !tryAgain {
				return err
			}
		}
	}
}

func pollUntilFinished(client *Client, instanceId string) error {
	return retry(15*time.Minute, 15*time.Second, func() (bool, error) {
		log.Println("Polling for async job")

		resp := client.LastOperation(instanceId)
		if resp.InError() {
			return false, resp.Error
		}

		if resp.StatusCode != 200 {
			log.Printf("Bad status code %d, needed 200", resp.StatusCode)
			return true, nil
		}

		var responseBody map[string]string
		err := json.Unmarshal(resp.ResponseBody, &responseBody)
		if err != nil {
			return false, err
		}

		state := responseBody["state"]
		eq := state == string(brokerapi.Succeeded)
		log.Printf("Last operation for %q was %q\n", instanceId, state)

		return !eq, nil

	})
}

func newExampleExecutor(client *Client, example broker.ServiceExample, service *broker.ServiceDefinition) (*exampleExecutor, error) {
	provisionParams, err := json.Marshal(example.ProvisionParams)
	if err != nil {
		return nil, err
	}

	bindParams, err := json.Marshal(example.BindParams)
	if err != nil {
		return nil, err
	}

	catalog, err := service.CatalogEntry()
	if err != nil {
		return nil, err
	}

	testid := rand.Uint32()

	return &exampleExecutor{
		Name:       fmt.Sprintf("%s/%s", service.Name, example.Name),
		ServiceId:  catalog.ID,
		PlanId:     example.PlanId,
		InstanceId: fmt.Sprintf("ex%d", testid),
		BindingId:  fmt.Sprintf("ex%d", testid),

		ProvisionParams: provisionParams,
		BindParams:      bindParams,

		client: client,
	}, nil
}

type exampleExecutor struct {
	Name string

	ServiceId  string
	PlanId     string
	InstanceId string
	BindingId  string

	ProvisionParams json.RawMessage
	BindParams      json.RawMessage

	client *Client
}

// Provision attempts to create a service instance from the example.
// Multiple calls to provision will attempt to create a resource with the same
// ServiceId and details.
// If the response is an async result, Provision will attempt to wait until
// the Provision is complete.<commit_msg>Refactor RunExample to run self-contained examples<commit_after>// Copyright 2018 the Service Broker Project Authors.
//
// 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 client

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/broker"
	"github.com/pivotal-cf/brokerapi"
)

// RunExamplesForService runs all the examples for a given service name against
// the service broker pointed to by client. All examples in the registry get run
// if serviceName is blank. If exampleName is non-blank then only the example
// with the given name is run.
func RunExamplesForService(registry broker.BrokerRegistry, client *Client, serviceName, exampleName string) error {
	rand.Seed(time.Now().UTC().UnixNano())

	services := registry.GetAllServices()

	// For all services available in the service broker
	for _, service := range services {
		if serviceName != "" && serviceName != service.Name {
			continue
		}

		for _, example := range service.Examples {
			if exampleName != "" && example.Name != exampleName {
				continue
			}

			serviceCatalogEntry, err := service.CatalogEntry()
			if err != nil {
				return err
			}

			var completeServiceExample = CompleteServiceExample{
				Example:                    example,
				ServiceId:                  serviceCatalogEntry.ID,
				ServiceName:                service.Name,
				ServiceBindOutputVariables: service.BindOutputVariables,
			}

			if err := RunExample(client, completeServiceExample); err != nil {
				return err
			}
		}
	}

	return nil

}

type CompleteServiceExample struct {
	Example                    broker.ServiceExample   `json: "service_example"`
	ServiceName                string                  `json: "service_name"`
	ServiceId                  string                  `json: "service_id"`
	ServiceBindOutputVariables []broker.BrokerVariable `json: "broker_variables"`
}

// RunExample runs a single example against the given service on the broker
// pointed to by client.
func RunExample(client *Client, serviceExample CompleteServiceExample) error {
	executor, err := newExampleExecutor(client, serviceExample)
	if err != nil {
		return err
	}

	executor.LogTestInfo()

	// Cleanup the test if it fails partway through
	defer func() {
		log.Println("Cleaning up the environment")
		executor.Unbind()
		executor.Deprovision()
	}()

	if err := executor.Provision(); err != nil {
		return err
	}

	bindResponse, err := executor.Bind()
	if err != nil {
		return err
	}

	if err := executor.Unbind(); err != nil {
		return err
	}

	if err := executor.Deprovision(); err != nil {
		return err
	}

	// Check that the binding response has the same fields as expected
	var binding brokerapi.Binding
	err = json.Unmarshal(bindResponse, &binding)
	if err != nil {
		return err
	}

	credentialsEntry := binding.Credentials.(map[string]interface{})
	if err := broker.ValidateVariables(credentialsEntry, serviceExample.ServiceBindOutputVariables); err != nil {
		log.Printf("Error: results don't match JSON Schema: %v", err)
		return err
	}

	return nil
}

func retry(timeout, period time.Duration, function func() (tryAgain bool, err error)) error {
	to := time.After(timeout)
	tick := time.Tick(period)

	if tryAgain, err := function(); !tryAgain {
		return err
	}

	// Keep trying until we're timed out or got a result or got an error
	for {
		select {
		case <-to:
			return errors.New("Timeout while waiting for result")
		case <-tick:
			tryAgain, err := function()

			if !tryAgain {
				return err
			}
		}
	}
}

func pollUntilFinished(client *Client, instanceId string) error {
	return retry(15*time.Minute, 15*time.Second, func() (bool, error) {
		log.Println("Polling for async job")

		resp := client.LastOperation(instanceId)
		if resp.InError() {
			return false, resp.Error
		}

		if resp.StatusCode != 200 {
			log.Printf("Bad status code %d, needed 200", resp.StatusCode)
			return true, nil
		}

		var responseBody map[string]string
		err := json.Unmarshal(resp.ResponseBody, &responseBody)
		if err != nil {
			return false, err
		}

		state := responseBody["state"]
		eq := state == string(brokerapi.Succeeded)
		log.Printf("Last operation for %q was %q\n", instanceId, state)

		return !eq, nil

	})
}

func newExampleExecutor(client *Client, serviceExample CompleteServiceExample) (*exampleExecutor, error) {
	provisionParams, err := json.Marshal(serviceExample.Example.ProvisionParams)
	if err != nil {
		return nil, err
	}

	bindParams, err := json.Marshal(serviceExample.Example.BindParams)
	if err != nil {
		return nil, err
	}

	testid := rand.Uint32()

	return &exampleExecutor{
		Name:       fmt.Sprintf("%s/%s", serviceExample.ServiceName, serviceExample.Example.Name),
		ServiceId:  serviceExample.ServiceId,
		PlanId:     serviceExample.Example.PlanId,
		InstanceId: fmt.Sprintf("ex%d", testid),
		BindingId:  fmt.Sprintf("ex%d", testid),

		ProvisionParams: provisionParams,
		BindParams:      bindParams,

		client: client,
	}, nil
}

type exampleExecutor struct {
	Name string

	ServiceId  string
	PlanId     string
	InstanceId string
	BindingId  string

	ProvisionParams json.RawMessage
	BindParams      json.RawMessage

	client *Client
}

// Provision attempts to create a service instance from the example.
// Multiple calls to provision will attempt to create a resource with the same
// ServiceId and details.
// If the response is an async result, Provision will attempt to wait until
// the Provision is complete.
{'fraction_non_alphanumeric': 0.10472972972972973, 'fraction_numerical': 0.0035561877667140826, 'mean_word_length': 3.574623830825539, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, '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': '2857870', 'n_tokens_mistral': 3588, 'n_tokens_neox': 3280, 'n_words': 1125}