<commit_before>/*
Copyright 2016 The Rook Authors. 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 file

import (
	"fmt"

	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/clusterd"
	"github.com/rook/rook/pkg/daemon/ceph/client"
	cephconfig "github.com/rook/rook/pkg/daemon/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/file/mds"
	"github.com/rook/rook/pkg/operator/ceph/pool"
	cephver "github.com/rook/rook/pkg/operator/ceph/version"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

const (
	dataPoolSuffix     = "data"
	metaDataPoolSuffix = "metadata"
	appName            = "cephfs"
)

// Filesystem represents an instance of a Ceph filesystem (CephFS)
type Filesystem struct {
	Name      string
	Namespace string
}

// createFilesystem creates a Ceph filesystem with metadata servers
func createFilesystem(
	clusterInfo *cephconfig.ClusterInfo,
	context *clusterd.Context,
	fs cephv1.CephFilesystem,
	clusterSpec *cephv1.ClusterSpec,
	ownerRefs metav1.OwnerReference,
	dataDirHostPath string,
	scheme *runtime.Scheme,
) error {

	if len(fs.Spec.DataPools) != 0 {
		f := newFS(fs.Name, fs.Namespace)
		if err := f.doFilesystemCreate(context, clusterInfo.CephVersion, fs.Spec); err != nil {
			return errors.Wrapf(err, "failed to create filesystem %q", fs.Name)
		}
	}

	filesystem, err := client.GetFilesystem(context, fs.Namespace, fs.Name)
	if err != nil {
		return errors.Wrapf(err, "failed to get filesystem %q", fs.Name)
	}

	if fs.Spec.MetadataServer.ActiveStandby {
		if err = client.AllowStandbyReplay(context, fs.Namespace, fs.Name, fs.Spec.MetadataServer.ActiveStandby); err != nil {
			return errors.Wrapf(err, "failed to set allow_standby_replay to filesystem %q", fs.Name)
		}
	}

	// set the number of active mds instances
	if fs.Spec.MetadataServer.ActiveCount > 1 {
		if err = client.SetNumMDSRanks(context, clusterInfo.CephVersion, fs.Namespace, fs.Name, fs.Spec.MetadataServer.ActiveCount); err != nil {
			logger.Warningf("failed setting active mds count to %d. %v", fs.Spec.MetadataServer.ActiveCount, err)
		}
	}

	logger.Infof("start running mdses for filesystem %q", fs.Name)
	c := mds.NewCluster(clusterInfo, context, clusterSpec, fs, filesystem, ownerRefs, dataDirHostPath, scheme)
	if err := c.Start(); err != nil {
		return err
	}

	return nil
}

// deleteFileSystem deletes the filesystem from Ceph
func deleteFilesystem(context *clusterd.Context, cephVersion cephver.CephVersion, fs cephv1.CephFilesystem) error {
	// The most important part of deletion is that the filesystem gets removed from Ceph
	// The K8s resources will already be removed with the K8s owner references
	if err := downFilesystem(context, fs.Namespace, fs.Name); err != nil {
		// If the fs isn't deleted from Ceph, leave the daemons so it can still be used.
		return errors.Wrapf(err, "failed to down filesystem %q", fs.Name)
	}

	// Permanently remove the filesystem if it was created by rook
	if len(fs.Spec.DataPools) != 0 {
		if err := client.RemoveFilesystem(context, fs.Namespace, fs.Name, fs.Spec.PreservePoolsOnDelete); err != nil {
			return errors.Wrapf(err, "failed to remove filesystem %q", fs.Name)
		}
	}
	return nil
}

func validateFilesystem(context *clusterd.Context, f *cephv1.CephFilesystem) error {
	if f.Name == "" {
		return errors.New("missing name")
	}
	if f.Namespace == "" {
		return errors.New("missing namespace")
	}
	if f.Spec.MetadataServer.ActiveCount < 1 {
		return errors.New("MetadataServer.ActiveCount must be at least 1")
	}
	// No data pool means that we expect the fs to exist already
	if len(f.Spec.DataPools) == 0 {
		return nil
	}
	if err := pool.ValidatePoolSpec(context, f.Namespace, &f.Spec.MetadataPool); err != nil {
		return errors.Wrapf(err, "invalid metadata pool")
	}
	for _, p := range f.Spec.DataPools {
		if err := pool.ValidatePoolSpec(context, f.Namespace, &p); err != nil {
			return errors.Wrapf(err, "Invalid data pool")
		}
	}

	return nil
}

// newFS creates a new instance of the file (MDS) service
func newFS(name, namespace string) *Filesystem {
	return &Filesystem{
		Name:      name,
		Namespace: namespace,
	}
}

// SetPoolSize function sets the sizes for MetadataPool and dataPool
func SetPoolSize(f *Filesystem, context *clusterd.Context, spec cephv1.FilesystemSpec) error {
	// generating the metadata pool's name
	metadataPoolName := generateMetaDataPoolName(f)
	err := client.CreatePoolWithProfile(context, f.Namespace, metadataPoolName, spec.MetadataPool, appName)
	if err != nil {
		return errors.Wrapf(err, "failed to update metadata pool %q", metadataPoolName)
	}
	// generating the data pool's name
	dataPoolNames := generateDataPoolNames(f, spec)
	for i, pool := range spec.DataPools {
		poolName := dataPoolNames[i]
		err := client.CreatePoolWithProfile(context, f.Namespace, poolName, pool, appName)
		if err != nil {
			return errors.Wrapf(err, "failed to update datapool  %q", poolName)
		}
	}
	return nil
}

// doFilesystemCreate starts the Ceph file daemons and creates the filesystem in Ceph.
func (f *Filesystem) doFilesystemCreate(context *clusterd.Context, cephVersion cephver.CephVersion, spec cephv1.FilesystemSpec) error {

	_, err := client.GetFilesystem(context, f.Namespace, f.Name)
	if err == nil {
		logger.Infof("filesystem %s already exists", f.Name)
		// Even if the fs already exists, the num active mdses may have changed

		if err := client.SetNumMDSRanks(context, cephVersion, f.Namespace, f.Name, spec.MetadataServer.ActiveCount); err != nil {
			logger.Errorf(
				fmt.Sprintf("failed to set num mds ranks (max_mds) to %d for filesystem %s, still continuing. ", spec.MetadataServer.ActiveCount, f.Name) +
					"this error is not critical, but mdses may not be as failure tolerant as desired. " +
					fmt.Sprintf("USER should verify that the number of active mdses is %d with 'ceph fs get %s'", spec.MetadataServer.ActiveCount, f.Name) +
					fmt.Sprintf(". %v", err),
			)
		}
		if err := SetPoolSize(f, context, spec); err != nil {
			return errors.Wrap(err, "failed to set pools size")
		}
		return nil
	}
	if len(spec.DataPools) == 0 {
		return errors.New("at least one data pool must be specified")
	}

	fslist, err := client.ListFilesystems(context, f.Namespace)
	if err != nil {
		return errors.Wrapf(err, "Unable to list existing filesystem")
	}
	if len(fslist) > 0 && !client.IsMultiFSEnabled() {
		return errors.Errorf("cannot create multiple filesystems. enable %s env variable to create more than one", client.MultiFsEnv)
	}

	poolNames, err := client.GetPoolNamesByID(context, f.Namespace)
	if err != nil {
		return errors.Wrapf(err, "failed to get pool names")
	}

	logger.Infof("Creating filesystem %s", f.Name)

	// Make easy to locate a pool by name and avoid repeated searches
	reversedPoolMap := make(map[string]int)
	for key, value := range poolNames {
		reversedPoolMap[value] = key
	}

	poolsCreated := false
	metadataPoolName := generateMetaDataPoolName(f)
	if _, poolFound := reversedPoolMap[metadataPoolName]; !poolFound {
		poolsCreated = true
		err = client.CreatePoolWithProfile(context, f.Namespace, metadataPoolName, spec.MetadataPool, "")
		if err != nil {
			return errors.Wrapf(err, "failed to create metadata pool %q", metadataPoolName)
		}
	}

	dataPoolNames := generateDataPoolNames(f, spec)
	for i, pool := range spec.DataPools {
		poolName := dataPoolNames[i]
		if _, poolFound := reversedPoolMap[poolName]; !poolFound {
			poolsCreated = true
			err = client.CreatePoolWithProfile(context, f.Namespace, poolName, pool, "")
			if err != nil {
				return errors.Wrapf(err, "failed to create data pool %q", poolName)
			}
			if pool.IsErasureCoded() {
				// An erasure coded data pool used for a filesystem must allow overwrites
				if err := client.SetPoolProperty(context, f.Namespace, poolName, "allow_ec_overwrites", "true"); err != nil {
					logger.Warningf("failed to set ec pool property. %v", err)
				}
			}
		}
	}

	// create the filesystem ('fs new' needs to be forced in order to reuse pre-existing pools)
	// if only one pool is created new it wont work (to avoid inconsistencies).
	if err := client.CreateFilesystem(context, f.Namespace, f.Name, metadataPoolName, dataPoolNames, !poolsCreated); err != nil {
		return err
	}

	logger.Infof("created filesystem %s on %d data pool(s) and metadata pool %s", f.Name, len(dataPoolNames), metadataPoolName)
	return nil
}

// downFilesystem marks the filesystem as down and the MDS' as failed
func downFilesystem(context *clusterd.Context, namespace, filesystemName string) error {
	logger.Infof("Downing filesystem %s", filesystemName)

	if err := client.FailFilesystem(context, namespace, filesystemName); err != nil {
		return err
	}
	logger.Infof("Downed filesystem %s", filesystemName)
	return nil
}

// generateDataPoolName generates DataPool name by prefixing the filesystem name to the constant DataPoolSuffix
func generateDataPoolNames(f *Filesystem, spec cephv1.FilesystemSpec) []string {
	var dataPoolNames []string
	for i := range spec.DataPools {
		poolName := fmt.Sprintf("%s-%s%d", f.Name, dataPoolSuffix, i)
		dataPoolNames = append(dataPoolNames, poolName)
	}
	return dataPoolNames
}

// generateMetaDataPoolName generates MetaDataPool name by prefixing the filesystem name to the constant metaDataPoolSuffix
func generateMetaDataPoolName(f *Filesystem) string {
	return fmt.Sprintf("%s-%s", f.Name, metaDataPoolSuffix)
}
<commit_msg>Ceph: PV provisioning fails upon upgrade<commit_after>/*
Copyright 2016 The Rook Authors. 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 file

import (
	"fmt"

	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/clusterd"
	"github.com/rook/rook/pkg/daemon/ceph/client"
	cephconfig "github.com/rook/rook/pkg/daemon/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/file/mds"
	"github.com/rook/rook/pkg/operator/ceph/pool"
	cephver "github.com/rook/rook/pkg/operator/ceph/version"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

const (
	dataPoolSuffix     = "data"
	metaDataPoolSuffix = "metadata"
)

// Filesystem represents an instance of a Ceph filesystem (CephFS)
type Filesystem struct {
	Name      string
	Namespace string
}

// createFilesystem creates a Ceph filesystem with metadata servers
func createFilesystem(
	clusterInfo *cephconfig.ClusterInfo,
	context *clusterd.Context,
	fs cephv1.CephFilesystem,
	clusterSpec *cephv1.ClusterSpec,
	ownerRefs metav1.OwnerReference,
	dataDirHostPath string,
	scheme *runtime.Scheme,
) error {

	if len(fs.Spec.DataPools) != 0 {
		f := newFS(fs.Name, fs.Namespace)
		if err := f.doFilesystemCreate(context, clusterInfo.CephVersion, fs.Spec); err != nil {
			return errors.Wrapf(err, "failed to create filesystem %q", fs.Name)
		}
	}

	filesystem, err := client.GetFilesystem(context, fs.Namespace, fs.Name)
	if err != nil {
		return errors.Wrapf(err, "failed to get filesystem %q", fs.Name)
	}

	if fs.Spec.MetadataServer.ActiveStandby {
		if err = client.AllowStandbyReplay(context, fs.Namespace, fs.Name, fs.Spec.MetadataServer.ActiveStandby); err != nil {
			return errors.Wrapf(err, "failed to set allow_standby_replay to filesystem %q", fs.Name)
		}
	}

	// set the number of active mds instances
	if fs.Spec.MetadataServer.ActiveCount > 1 {
		if err = client.SetNumMDSRanks(context, clusterInfo.CephVersion, fs.Namespace, fs.Name, fs.Spec.MetadataServer.ActiveCount); err != nil {
			logger.Warningf("failed setting active mds count to %d. %v", fs.Spec.MetadataServer.ActiveCount, err)
		}
	}

	logger.Infof("start running mdses for filesystem %q", fs.Name)
	c := mds.NewCluster(clusterInfo, context, clusterSpec, fs, filesystem, ownerRefs, dataDirHostPath, scheme)
	if err := c.Start(); err != nil {
		return err
	}

	return nil
}

// deleteFileSystem deletes the filesystem from Ceph
func deleteFilesystem(context *clusterd.Context, cephVersion cephver.CephVersion, fs cephv1.CephFilesystem) error {
	// The most important part of deletion is that the filesystem gets removed from Ceph
	// The K8s resources will already be removed with the K8s owner references
	if err := downFilesystem(context, fs.Namespace, fs.Name); err != nil {
		// If the fs isn't deleted from Ceph, leave the daemons so it can still be used.
		return errors.Wrapf(err, "failed to down filesystem %q", fs.Name)
	}

	// Permanently remove the filesystem if it was created by rook
	if len(fs.Spec.DataPools) != 0 {
		if err := client.RemoveFilesystem(context, fs.Namespace, fs.Name, fs.Spec.PreservePoolsOnDelete); err != nil {
			return errors.Wrapf(err, "failed to remove filesystem %q", fs.Name)
		}
	}
	return nil
}

func validateFilesystem(context *clusterd.Context, f *cephv1.CephFilesystem) error {
	if f.Name == "" {
		return errors.New("missing name")
	}
	if f.Namespace == "" {
		return errors.New("missing namespace")
	}
	if f.Spec.MetadataServer.ActiveCount < 1 {
		return errors.New("MetadataServer.ActiveCount must be at least 1")
	}
	// No data pool means that we expect the fs to exist already
	if len(f.Spec.DataPools) == 0 {
		return nil
	}
	if err := pool.ValidatePoolSpec(context, f.Namespace, &f.Spec.MetadataPool); err != nil {
		return errors.Wrapf(err, "invalid metadata pool")
	}
	for _, p := range f.Spec.DataPools {
		if err := pool.ValidatePoolSpec(context, f.Namespace, &p); err != nil {
			return errors.Wrapf(err, "Invalid data pool")
		}
	}

	return nil
}

// newFS creates a new instance of the file (MDS) service
func newFS(name, namespace string) *Filesystem {
	return &Filesystem{
		Name:      name,
		Namespace: namespace,
	}
}

// SetPoolSize function sets the sizes for MetadataPool and dataPool
func SetPoolSize(f *Filesystem, context *clusterd.Context, spec cephv1.FilesystemSpec) error {
	// generating the metadata pool's name
	metadataPoolName := generateMetaDataPoolName(f)
	err := client.CreatePoolWithProfile(context, f.Namespace, metadataPoolName, spec.MetadataPool, "")
	if err != nil {
		return errors.Wrapf(err, "failed to update metadata pool %q", metadataPoolName)
	}
	// generating the data pool's name
	dataPoolNames := generateDataPoolNames(f, spec)
	for i, pool := range spec.DataPools {
		poolName := dataPoolNames[i]
		err := client.CreatePoolWithProfile(context, f.Namespace, poolName, pool, "")
		if err != nil {
			return errors.Wrapf(err, "failed to update datapool  %q", poolName)
		}
	}
	return nil
}

// doFilesystemCreate starts the Ceph file daemons and creates the filesystem in Ceph.
func (f *Filesystem) doFilesystemCreate(context *clusterd.Context, cephVersion cephver.CephVersion, spec cephv1.FilesystemSpec) error {

	_, err := client.GetFilesystem(context, f.Namespace, f.Name)
	if err == nil {
		logger.Infof("filesystem %s already exists", f.Name)
		// Even if the fs already exists, the num active mdses may have changed

		if err := client.SetNumMDSRanks(context, cephVersion, f.Namespace, f.Name, spec.MetadataServer.ActiveCount); err != nil {
			logger.Errorf(
				fmt.Sprintf("failed to set num mds ranks (max_mds) to %d for filesystem %s, still continuing. ", spec.MetadataServer.ActiveCount, f.Name) +
					"this error is not critical, but mdses may not be as failure tolerant as desired. " +
					fmt.Sprintf("USER should verify that the number of active mdses is %d with 'ceph fs get %s'", spec.MetadataServer.ActiveCount, f.Name) +
					fmt.Sprintf(". %v", err),
			)
		}
		if err := SetPoolSize(f, context, spec); err != nil {
			return errors.Wrap(err, "failed to set pools size")
		}
		return nil
	}
	if len(spec.DataPools) == 0 {
		return errors.New("at least one data pool must be specified")
	}

	fslist, err := client.ListFilesystems(context, f.Namespace)
	if err != nil {
		return errors.Wrapf(err, "Unable to list existing filesystem")
	}
	if len(fslist) > 0 && !client.IsMultiFSEnabled() {
		return errors.Errorf("cannot create multiple filesystems. enable %s env variable to create more than one", client.MultiFsEnv)
	}

	poolNames, err := client.GetPoolNamesByID(context, f.Namespace)
	if err != nil {
		return errors.Wrapf(err, "failed to get pool names")
	}

	logger.Infof("Creating filesystem %s", f.Name)

	// Make easy to locate a pool by name and avoid repeated searches
	reversedPoolMap := make(map[string]int)
	for key, value := range poolNames {
		reversedPoolMap[value] = key
	}

	poolsCreated := false
	metadataPoolName := generateMetaDataPoolName(f)
	if _, poolFound := reversedPoolMap[metadataPoolName]; !poolFound {
		poolsCreated = true
		err = client.CreatePoolWithProfile(context, f.Namespace, metadataPoolName, spec.MetadataPool, "")
		if err != nil {
			return errors.Wrapf(err, "failed to create metadata pool %q", metadataPoolName)
		}
	}

	dataPoolNames := generateDataPoolNames(f, spec)
	for i, pool := range spec.DataPools {
		poolName := dataPoolNames[i]
		if _, poolFound := reversedPoolMap[poolName]; !poolFound {
			poolsCreated = true
			err = client.CreatePoolWithProfile(context, f.Namespace, poolName, pool, "")
			if err != nil {
				return errors.Wrapf(err, "failed to create data pool %q", poolName)
			}
			if pool.IsErasureCoded() {
				// An erasure coded data pool used for a filesystem must allow overwrites
				if err := client.SetPoolProperty(context, f.Namespace, poolName, "allow_ec_overwrites", "true"); err != nil {
					logger.Warningf("failed to set ec pool property. %v", err)
				}
			}
		}
	}

	// create the filesystem ('fs new' needs to be forced in order to reuse pre-existing pools)
	// if only one pool is created new it wont work (to avoid inconsistencies).
	if err := client.CreateFilesystem(context, f.Namespace, f.Name, metadataPoolName, dataPoolNames, !poolsCreated); err != nil {
		return err
	}

	logger.Infof("created filesystem %s on %d data pool(s) and metadata pool %s", f.Name, len(dataPoolNames), metadataPoolName)
	return nil
}

// downFilesystem marks the filesystem as down and the MDS' as failed
func downFilesystem(context *clusterd.Context, namespace, filesystemName string) error {
	logger.Infof("Downing filesystem %s", filesystemName)

	if err := client.FailFilesystem(context, namespace, filesystemName); err != nil {
		return err
	}
	logger.Infof("Downed filesystem %s", filesystemName)
	return nil
}

// generateDataPoolName generates DataPool name by prefixing the filesystem name to the constant DataPoolSuffix
func generateDataPoolNames(f *Filesystem, spec cephv1.FilesystemSpec) []string {
	var dataPoolNames []string
	for i := range spec.DataPools {
		poolName := fmt.Sprintf("%s-%s%d", f.Name, dataPoolSuffix, i)
		dataPoolNames = append(dataPoolNames, poolName)
	}
	return dataPoolNames
}

// generateMetaDataPoolName generates MetaDataPool name by prefixing the filesystem name to the constant metaDataPoolSuffix
func generateMetaDataPoolName(f *Filesystem) string {
	return fmt.Sprintf("%s-%s", f.Name, metaDataPoolSuffix)
}
<|endoftext|>


<commit_before>/*
 *  Copyright 2016 Adobe Systems Incorporated. All rights reserved.
 *  This file is licensed to you 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 REPRESENTATIONS
 *  OF ANY KIND, either express or implied. See the License for the specific language
 *  governing permissions and limitations under the License.
 */
package hook

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"strings"
	"sync/atomic"

	"github.com/adobe-platform/porter/aws/elb"
	"github.com/adobe-platform/porter/aws_session"
	"github.com/adobe-platform/porter/conf"
	"github.com/adobe-platform/porter/constants"
	"github.com/adobe-platform/porter/provision_output"
	"github.com/inconshreveable/log15"
)

type (
	Opts struct {
		BuildStdout io.Writer
		BuildStderr io.Writer

		RunStdout io.Writer
		RunStderr io.Writer
	}
)

// Multi-region deployment means we need an additional unique id for git clones
// and image names
var globalCounter *uint32 = new(uint32)

func newOpts() *Opts {
	return &Opts{
		BuildStdout: os.Stdout,
		BuildStderr: os.Stderr,

		RunStdout: os.Stdout,
		RunStderr: os.Stderr,
	}
}

func Execute(log log15.Logger,
	hookName, environment string,
	provisionedRegions []provision_output.Region,
	fs ...func(*Opts)) (success bool) {

	var err error

	log = log.New("HookName", hookName)
	log.Info("Hook BEGIN")

	config, configSuccess := conf.GetConfig(log, false)
	if !configSuccess {
		return
	}

	opts := newOpts()
	for _, f := range fs {
		f(opts)
	}

	workingDir, err := os.Getwd()
	if err != nil {
		log.Error("Getwd", "Error", err)
		return
	}
	log.Debug("os.Getwd()", "Path", workingDir)

	var configHooks []conf.Hook

	configHooks, exists := config.Hooks[hookName]
	if !exists {
		log.Error("Hook is undefined")
		return
	}

	if environment == "" {

		runArgs := runArgsFactory(log, config, workingDir)

		if !runConfigHooks(log, config, hookName, configHooks, runArgs, opts) {
			return
		}

	} else {

		env, err := config.GetEnvironment(environment)
		if err != nil {
			log.Error("GetEnvironment", "Error", err)
			return
		}

		// This applies to pre-provision which doesn't have provisioning output
		// since the provision command hasn't run but should work multi-region
		// in the same way that post-provision and others behave
		if provisionedRegions == nil {
			provisionedRegions = make([]provision_output.Region, 0)

			for _, region := range env.Regions {
				pr := provision_output.Region{
					AWSRegion: region.Name,
				}
				provisionedRegions = append(provisionedRegions, pr)
			}
		}

		for _, pr := range provisionedRegions {

			elbDNS := ""

			region, err := env.GetRegion(pr.AWSRegion)
			if err != nil {
				log.Error("GetRegion", "Error", err)
				return
			}

			roleARN, err := env.GetRoleARN(region.Name)
			if err != nil {
				log.Error("GetRoleARN", "Error", err)
				return
			}

			roleSession := aws_session.STS(region.Name, roleARN, 3600)

			if pr.ProvisionedELBName != "" {
				elbClient := elb.New(roleSession)

				log.Info("DescribeLoadBalancers")
				output, err := elb.DescribeLoadBalancers(elbClient, pr.ProvisionedELBName)
				if err != nil {
					log.Error("DescribeLoadBalancers", "Error", err)
					return
				}
				if len(output) == 1 {
					elbDNS = *output[0].DNSName
				} else {
					log.Warn("DescribeLoadBalancers - no ELB found")
				}
			}

			credValue, err := roleSession.Config.Credentials.Get()
			if err != nil {
				log.Warn("Couldn't get AWS credential values. Hooks calling AWS APIs will fail")
			}

			runArgs := runArgsFactory(log, config, workingDir)

			runArgs = append(runArgs,
				"-e", "PORTER_ENVIRONMENT="+environment,
				"-e", "AWS_REGION="+region.Name,
				// AWS_DEFAULT_REGION is also needed for AWS SDKs
				"-e", "AWS_DEFAULT_REGION="+region.Name,
				"-e", "AWS_ACCESS_KEY_ID="+credValue.AccessKeyID,
				"-e", "AWS_SECRET_ACCESS_KEY="+credValue.SecretAccessKey,
				"-e", "AWS_SESSION_TOKEN="+credValue.SessionToken,
				"-e", "AWS_SECURITY_TOKEN="+credValue.SessionToken,
			)

			if elbDNS != "" {
				runArgs = append(runArgs,
					"-e", "AWS_ELASTICLOADBALANCING_LOADBALANCER_DNS="+elbDNS)
			}

			if pr.StackId != "" {
				runArgs = append(runArgs,
					"-e", "AWS_CLOUDFORMATION_STACKID="+pr.StackId)
			}

			if !runConfigHooks(log, config, hookName, configHooks, runArgs, opts) {
				return
			}
		}
	}

	log.Info("Hook END")

	success = true
	return
}

func runArgsFactory(log log15.Logger, config *conf.Config, workingDir string) []string {
	runArgs := []string{
		"run",
		"--rm",
		"-v", fmt.Sprintf("%s:%s", workingDir, "/repo_root"),
		"-e", "PORTER_SERVICE_NAME=" + config.ServiceName,
		"-e", "DOCKER_ENV_FILE=" + constants.EnvFile,
		"-e", "HAPROXY_STATS_USERNAME=" + constants.HAProxyStatsUsername,
		"-e", "HAPROXY_STATS_PASSWORD=" + constants.HAProxyStatsPassword,
		"-e", "HAPROXY_STATS_URL=" + constants.HAProxyStatsUrl,
	}

	revParseOutput, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
	if err == nil {
		sha1 := strings.TrimSpace(string(revParseOutput))

		runArgs = append(runArgs, "-e", "PORTER_SERVICE_VERSION="+sha1)
	}

	var warnedDeprecation bool
	for _, kvp := range os.Environ() {
		if strings.HasPrefix(kvp, "PORTER_") {
			if !warnedDeprecation {
				warnedDeprecation = true
				log.Warn("Hook environments configured with PORTER_ is deprecated. In future releases and this will be an error http://bit.ly/2ar6fcQ")
			}
			log.Debug("Deprecated environment", "Env", kvp)
			runArgs = append(runArgs, "-e", strings.TrimPrefix(kvp, "PORTER_"))
		}
	}

	return runArgs
}

type hookLinkedList struct {
	hook      conf.Hook
	hookIndex int

	next *hookLinkedList
}

func runConfigHooks(log log15.Logger, config *conf.Config, hookName string,
	hooks []conf.Hook, runArgs []string, opts *Opts) (success bool) {

	successChan := make(chan bool)
	var (
		concurrentCount int
		configSuccess   bool

		head *hookLinkedList
		tail *hookLinkedList
	)

	// Only retained lexical scope is successChan, everything else is copied
	goGadgetHook := func(log log15.Logger, config conf.Config, hookName string,
		hookIndex int, hook conf.Hook, runArgs []string, opts *Opts) {

		successChan <- runConfigHook(log, config, hookName, hookIndex, hook, runArgs, opts)
	}

	for hookIndex, hook := range hooks {
		next := &hookLinkedList{
			hook:      hook,
			hookIndex: hookIndex,
		}

		if head == nil {
			head = next
		}

		if tail == nil {
			tail = head
		} else {
			tail.next = next
			tail = next
		}
	}

	for node := head; node != nil; node = node.next {

		if node.hook.Concurrent {

			concurrentCount++
		} else {

			concurrentCount = 1
		}

		log := log.New("HookIndex", node.hookIndex, "Concurrent", node.hook.Concurrent)
		log.Debug("go go gadget hook")
		go goGadgetHook(log, *config, hookName, node.hookIndex, node.hook, runArgs, opts)

		// block anytime we're not running consecutive concurrent hooks
		if node.next == nil || !node.next.hook.Concurrent {

			log.Debug("Waiting for hook(s) to finish", "concurrentCount", concurrentCount)
			for i := 0; i < concurrentCount; i++ {
				success = <-successChan
				if !success {
					return
				}
			}
			log.Debug("Hook(s) finished", "concurrentCount", concurrentCount)

			config, configSuccess = conf.GetConfig(log, false)
			if !configSuccess {
				return
			}

			concurrentCount = 0
		}
	}

	success = true
	return
}

func runConfigHook(log log15.Logger, config conf.Config, hookName string,
	hookIndex int, hook conf.Hook, runArgs []string, opts *Opts) (success bool) {
	log.Debug("runConfigHook() BEGIN")
	defer log.Debug("runConfigHook() END")

	for envKey, envValue := range hook.Environment {
		if envValue == "" {
			envValue = os.Getenv(envKey)
		}
		runArgs = append(runArgs, "-e", envKey+"="+envValue)
		log.Debug("Configured environment", "Key", envKey, "Value", envValue)
	}

	dockerFilePath := hook.Dockerfile
	hookCounter := atomic.AddUint32(globalCounter, 1)

	if hook.Repo != "" {

		repoDir := fmt.Sprintf("%s_clone_%d_%d", hookName, hookIndex, hookCounter)
		repoDir = path.Join(constants.TempDir, repoDir)

		defer exec.Command("rm", "-fr", repoDir).Run()

		log.Info("git clone",
			"Repo", hook.Repo,
			"Ref", hook.Ref,
			"Directory", repoDir,
		)

		cloneCmd := exec.Command(
			"git", "clone",
			"--branch", hook.Ref, // this works on tags as well
			"--depth", "1",
			hook.Repo, repoDir,
		)
		cloneCmd.Stderr = os.Stderr
		err := cloneCmd.Run()
		if err != nil {
			log.Error("git clone", "Error", err)
			return
		}

		// Validation ensures that local hooks have a dockerfile path
		// Plugins default to Dockerfile
		if dockerFilePath == "" {
			dockerFilePath = "Dockerfile"
		}

		dockerFilePath = path.Join(repoDir, dockerFilePath)
	}

	imageName := fmt.Sprintf("%s-%s-%d-%d", config.ServiceName, hookName, hookIndex, hookCounter)

	if !buildAndRun(log, imageName, dockerFilePath, runArgs, opts) {
		return
	}

	success = true
	return
}

func buildAndRun(log log15.Logger, imageName, dockerFilePath string,
	runArgs []string, opts *Opts) (success bool) {

	log = log.New("Dockerfile", dockerFilePath, "ImageName", imageName)

	log.Debug("buildAndRun() BEGIN")
	defer log.Debug("buildAndRun() END")
	log.Info("docker build")

	dockerBuildCmd := exec.Command("docker", "build",
		"-t", imageName,
		"-f", dockerFilePath,
		path.Dir(dockerFilePath),
	)
	dockerBuildCmd.Stdout = opts.BuildStdout
	dockerBuildCmd.Stderr = opts.BuildStderr

	err := dockerBuildCmd.Run()
	if err != nil {
		log.Error("docker build", "Dockerfile", dockerFilePath, "Error", err)
		return
	}

	runArgs = append(runArgs, imageName)

	runCmd := exec.Command("docker", runArgs...)
	runCmd.Stdout = opts.RunStdout
	runCmd.Stderr = opts.RunStderr

	log.Debug("docker run", "Args", runArgs)

	err = runCmd.Run()
	if err != nil {
		log.Error("docker run", "Error", err)
		return
	}

	success = true
	return
}
<commit_msg>not all hooks will be defined<commit_after>/*
 *  Copyright 2016 Adobe Systems Incorporated. All rights reserved.
 *  This file is licensed to you 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 REPRESENTATIONS
 *  OF ANY KIND, either express or implied. See the License for the specific language
 *  governing permissions and limitations under the License.
 */
package hook

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"strings"
	"sync/atomic"

	"github.com/adobe-platform/porter/aws/elb"
	"github.com/adobe-platform/porter/aws_session"
	"github.com/adobe-platform/porter/conf"
	"github.com/adobe-platform/porter/constants"
	"github.com/adobe-platform/porter/provision_output"
	"github.com/inconshreveable/log15"
)

type (
	Opts struct {
		BuildStdout io.Writer
		BuildStderr io.Writer

		RunStdout io.Writer
		RunStderr io.Writer
	}
)

// Multi-region deployment means we need an additional unique id for git clones
// and image names
var globalCounter *uint32 = new(uint32)

func newOpts() *Opts {
	return &Opts{
		BuildStdout: os.Stdout,
		BuildStderr: os.Stderr,

		RunStdout: os.Stdout,
		RunStderr: os.Stderr,
	}
}

func Execute(log log15.Logger,
	hookName, environment string,
	provisionedRegions []provision_output.Region,
	fs ...func(*Opts)) (success bool) {

	var err error

	log = log.New("HookName", hookName)
	log.Info("Hook BEGIN")

	config, configSuccess := conf.GetConfig(log, false)
	if !configSuccess {
		return
	}

	opts := newOpts()
	for _, f := range fs {
		f(opts)
	}

	workingDir, err := os.Getwd()
	if err != nil {
		log.Error("Getwd", "Error", err)
		return
	}
	log.Debug("os.Getwd()", "Path", workingDir)

	var configHooks []conf.Hook

	configHooks, exists := config.Hooks[hookName]
	if !exists {
		success = true
		return
	}

	if environment == "" {

		runArgs := runArgsFactory(log, config, workingDir)

		if !runConfigHooks(log, config, hookName, configHooks, runArgs, opts) {
			return
		}

	} else {

		env, err := config.GetEnvironment(environment)
		if err != nil {
			log.Error("GetEnvironment", "Error", err)
			return
		}

		// This applies to pre-provision which doesn't have provisioning output
		// since the provision command hasn't run but should work multi-region
		// in the same way that post-provision and others behave
		if provisionedRegions == nil {
			provisionedRegions = make([]provision_output.Region, 0)

			for _, region := range env.Regions {
				pr := provision_output.Region{
					AWSRegion: region.Name,
				}
				provisionedRegions = append(provisionedRegions, pr)
			}
		}

		for _, pr := range provisionedRegions {

			elbDNS := ""

			region, err := env.GetRegion(pr.AWSRegion)
			if err != nil {
				log.Error("GetRegion", "Error", err)
				return
			}

			roleARN, err := env.GetRoleARN(region.Name)
			if err != nil {
				log.Error("GetRoleARN", "Error", err)
				return
			}

			roleSession := aws_session.STS(region.Name, roleARN, 3600)

			if pr.ProvisionedELBName != "" {
				elbClient := elb.New(roleSession)

				log.Info("DescribeLoadBalancers")
				output, err := elb.DescribeLoadBalancers(elbClient, pr.ProvisionedELBName)
				if err != nil {
					log.Error("DescribeLoadBalancers", "Error", err)
					return
				}
				if len(output) == 1 {
					elbDNS = *output[0].DNSName
				} else {
					log.Warn("DescribeLoadBalancers - no ELB found")
				}
			}

			credValue, err := roleSession.Config.Credentials.Get()
			if err != nil {
				log.Warn("Couldn't get AWS credential values. Hooks calling AWS APIs will fail")
			}

			runArgs := runArgsFactory(log, config, workingDir)

			runArgs = append(runArgs,
				"-e", "PORTER_ENVIRONMENT="+environment,
				"-e", "AWS_REGION="+region.Name,
				// AWS_DEFAULT_REGION is also needed for AWS SDKs
				"-e", "AWS_DEFAULT_REGION="+region.Name,
				"-e", "AWS_ACCESS_KEY_ID="+credValue.AccessKeyID,
				"-e", "AWS_SECRET_ACCESS_KEY="+credValue.SecretAccessKey,
				"-e", "AWS_SESSION_TOKEN="+credValue.SessionToken,
				"-e", "AWS_SECURITY_TOKEN="+credValue.SessionToken,
			)

			if elbDNS != "" {
				runArgs = append(runArgs,
					"-e", "AWS_ELASTICLOADBALANCING_LOADBALANCER_DNS="+elbDNS)
			}

			if pr.StackId != "" {
				runArgs = append(runArgs,
					"-e", "AWS_CLOUDFORMATION_STACKID="+pr.StackId)
			}

			if !runConfigHooks(log, config, hookName, configHooks, runArgs, opts) {
				return
			}
		}
	}

	log.Info("Hook END")

	success = true
	return
}

func runArgsFactory(log log15.Logger, config *conf.Config, workingDir string) []string {
	runArgs := []string{
		"run",
		"--rm",
		"-v", fmt.Sprintf("%s:%s", workingDir, "/repo_root"),
		"-e", "PORTER_SERVICE_NAME=" + config.ServiceName,
		"-e", "DOCKER_ENV_FILE=" + constants.EnvFile,
		"-e", "HAPROXY_STATS_USERNAME=" + constants.HAProxyStatsUsername,
		"-e", "HAPROXY_STATS_PASSWORD=" + constants.HAProxyStatsPassword,
		"-e", "HAPROXY_STATS_URL=" + constants.HAProxyStatsUrl,
	}

	revParseOutput, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
	if err == nil {
		sha1 := strings.TrimSpace(string(revParseOutput))

		runArgs = append(runArgs, "-e", "PORTER_SERVICE_VERSION="+sha1)
	}

	var warnedDeprecation bool
	for _, kvp := range os.Environ() {
		if strings.HasPrefix(kvp, "PORTER_") {
			if !warnedDeprecation {
				warnedDeprecation = true
				log.Warn("Hook environments configured with PORTER_ is deprecated. In future releases and this will be an error http://bit.ly/2ar6fcQ")
			}
			log.Debug("Deprecated environment", "Env", kvp)
			runArgs = append(runArgs, "-e", strings.TrimPrefix(kvp, "PORTER_"))
		}
	}

	return runArgs
}

type hookLinkedList struct {
	hook      conf.Hook
	hookIndex int

	next *hookLinkedList
}

func runConfigHooks(log log15.Logger, config *conf.Config, hookName string,
	hooks []conf.Hook, runArgs []string, opts *Opts) (success bool) {

	successChan := make(chan bool)
	var (
		concurrentCount int
		configSuccess   bool

		head *hookLinkedList
		tail *hookLinkedList
	)

	// Only retained lexical scope is successChan, everything else is copied
	goGadgetHook := func(log log15.Logger, config conf.Config, hookName string,
		hookIndex int, hook conf.Hook, runArgs []string, opts *Opts) {

		successChan <- runConfigHook(log, config, hookName, hookIndex, hook, runArgs, opts)
	}

	for hookIndex, hook := range hooks {
		next := &hookLinkedList{
			hook:      hook,
			hookIndex: hookIndex,
		}

		if head == nil {
			head = next
		}

		if tail == nil {
			tail = head
		} else {
			tail.next = next
			tail = next
		}
	}

	for node := head; node != nil; node = node.next {

		if node.hook.Concurrent {

			concurrentCount++
		} else {

			concurrentCount = 1
		}

		log := log.New("HookIndex", node.hookIndex, "Concurrent", node.hook.Concurrent)
		log.Debug("go go gadget hook")
		go goGadgetHook(log, *config, hookName, node.hookIndex, node.hook, runArgs, opts)

		// block anytime we're not running consecutive concurrent hooks
		if node.next == nil || !node.next.hook.Concurrent {

			log.Debug("Waiting for hook(s) to finish", "concurrentCount", concurrentCount)
			for i := 0; i < concurrentCount; i++ {
				success = <-successChan
				if !success {
					return
				}
			}
			log.Debug("Hook(s) finished", "concurrentCount", concurrentCount)

			config, configSuccess = conf.GetConfig(log, false)
			if !configSuccess {
				return
			}

			concurrentCount = 0
		}
	}

	success = true
	return
}

func runConfigHook(log log15.Logger, config conf.Config, hookName string,
	hookIndex int, hook conf.Hook, runArgs []string, opts *Opts) (success bool) {
	log.Debug("runConfigHook() BEGIN")
	defer log.Debug("runConfigHook() END")

	for envKey, envValue := range hook.Environment {
		if envValue == "" {
			envValue = os.Getenv(envKey)
		}
		runArgs = append(runArgs, "-e", envKey+"="+envValue)
		log.Debug("Configured environment", "Key", envKey, "Value", envValue)
	}

	dockerFilePath := hook.Dockerfile
	hookCounter := atomic.AddUint32(globalCounter, 1)

	if hook.Repo != "" {

		repoDir := fmt.Sprintf("%s_clone_%d_%d", hookName, hookIndex, hookCounter)
		repoDir = path.Join(constants.TempDir, repoDir)

		defer exec.Command("rm", "-fr", repoDir).Run()

		log.Info("git clone",
			"Repo", hook.Repo,
			"Ref", hook.Ref,
			"Directory", repoDir,
		)

		cloneCmd := exec.Command(
			"git", "clone",
			"--branch", hook.Ref, // this works on tags as well
			"--depth", "1",
			hook.Repo, repoDir,
		)
		cloneCmd.Stderr = os.Stderr
		err := cloneCmd.Run()
		if err != nil {
			log.Error("git clone", "Error", err)
			return
		}

		// Validation ensures that local hooks have a dockerfile path
		// Plugins default to Dockerfile
		if dockerFilePath == "" {
			dockerFilePath = "Dockerfile"
		}

		dockerFilePath = path.Join(repoDir, dockerFilePath)
	}

	imageName := fmt.Sprintf("%s-%s-%d-%d", config.ServiceName, hookName, hookIndex, hookCounter)

	if !buildAndRun(log, imageName, dockerFilePath, runArgs, opts) {
		return
	}

	success = true
	return
}

func buildAndRun(log log15.Logger, imageName, dockerFilePath string,
	runArgs []string, opts *Opts) (success bool) {

	log = log.New("Dockerfile", dockerFilePath, "ImageName", imageName)

	log.Debug("buildAndRun() BEGIN")
	defer log.Debug("buildAndRun() END")
	log.Info("docker build")

	dockerBuildCmd := exec.Command("docker", "build",
		"-t", imageName,
		"-f", dockerFilePath,
		path.Dir(dockerFilePath),
	)
	dockerBuildCmd.Stdout = opts.BuildStdout
	dockerBuildCmd.Stderr = opts.BuildStderr

	err := dockerBuildCmd.Run()
	if err != nil {
		log.Error("docker build", "Dockerfile", dockerFilePath, "Error", err)
		return
	}

	runArgs = append(runArgs, imageName)

	runCmd := exec.Command("docker", runArgs...)
	runCmd.Stdout = opts.RunStdout
	runCmd.Stderr = opts.RunStderr

	log.Debug("docker run", "Args", runArgs)

	err = runCmd.Run()
	if err != nil {
		log.Error("docker run", "Error", err)
		return
	}

	success = true
	return
}
<|endoftext|>


<commit_before>package version

import (
	"fmt"
	"regexp"
	"strconv"
)

type CephVersion struct {
	Major int
	Minor int
	Extra int
}

const (
	unknownVersionString = "<unknown version>"
)

var (
	Luminous = CephVersion{12, 0, 0}
	Mimic    = CephVersion{13, 0, 0}
	Nautilus = CephVersion{14, 0, 0}

	// supportedVersions are production-ready versions that rook supports
	supportedVersions   = []CephVersion{Luminous, Mimic}
	unsupportedVersions = []CephVersion{Nautilus}
	// allVersions includes all supportedVersions as well as unreleased versions that are being tested with rook
	allVersions = append(supportedVersions, unsupportedVersions...)

	// for parsing the output of `ceph --version`
	versionPattern = regexp.MustCompile(`ceph version (\d+)\.(\d+)\.(\d+)`)
)

func (v *CephVersion) String() string {
	return fmt.Sprintf("%d.%d.%d %s",
		v.Major, v.Minor, v.Extra, v.ReleaseName())
}

func (v *CephVersion) ReleaseName() string {
	switch v.Major {
	case Nautilus.Major:
		return "nautilus"
	case Mimic.Major:
		return "mimic"
	case Luminous.Major:
		return "luminous"
	default:
		return unknownVersionString
	}
}

func ExtractCephVersion(src string) (*CephVersion, error) {
	m := versionPattern.FindStringSubmatch(src)
	if m == nil {
		return nil, fmt.Errorf("failed to parse version from: %s", src)
	}

	major, err := strconv.Atoi(m[1])
	if err != nil {
		return nil, fmt.Errorf("failed to parse version major part: %s", m[0])
	}

	minor, err := strconv.Atoi(m[2])
	if err != nil {
		return nil, fmt.Errorf("failed to parse version minor part: %s", m[1])
	}

	extra, err := strconv.Atoi(m[3])
	if err != nil {
		return nil, fmt.Errorf("failed to parse version extra part: %s", m[2])
	}

	return &CephVersion{major, minor, extra}, nil
}

func (v *CephVersion) Supported() bool {
	for _, sv := range supportedVersions {
		if v.isRelease(sv) {
			return true
		}
	}
	return false
}

func (v *CephVersion) isRelease(other CephVersion) bool {
	return v.Major == other.Major
}

func (v *CephVersion) IsLuminous() bool {
	return v.isRelease(Luminous)
}

func (v *CephVersion) IsAtLeast(other CephVersion) bool {
	if v.Major > other.Major {
		return true
	} else if v.Major < other.Major {
		return false
	}
	if v.Minor > other.Minor {
		return true
	} else if v.Minor < other.Minor {
		return false
	}
	if v.Extra > other.Extra {
		return true
	} else if v.Extra < other.Extra {
		return false
	}
	return true
}

func (v *CephVersion) IsAtLeastNautilus() bool {
	return v.IsAtLeast(Nautilus)
}

func (v *CephVersion) IsAtLeastMimic() bool {
	return v.IsAtLeast(Mimic)
}
<commit_msg>ceph: add support octopus version detection<commit_after>package version

import (
	"fmt"
	"regexp"
	"strconv"
)

type CephVersion struct {
	Major int
	Minor int
	Extra int
}

const (
	unknownVersionString = "<unknown version>"
)

var (
	// Luminous Ceph version
	Luminous = CephVersion{12, 0, 0}
	// Mimic Ceph version
	Mimic = CephVersion{13, 0, 0}
	// Nautilus Ceph version
	Nautilus = CephVersion{14, 0, 0}
	// Octopus Ceph version
	Octopus = CephVersion{15, 0, 0}

	// supportedVersions are production-ready versions that rook supports
	supportedVersions   = []CephVersion{Luminous, Mimic}
	unsupportedVersions = []CephVersion{Nautilus}
	// allVersions includes all supportedVersions as well as unreleased versions that are being tested with rook
	allVersions = append(supportedVersions, unsupportedVersions...)

	// for parsing the output of `ceph --version`
	versionPattern = regexp.MustCompile(`ceph version (\d+)\.(\d+)\.(\d+)`)
)

func (v *CephVersion) String() string {
	return fmt.Sprintf("%d.%d.%d %s",
		v.Major, v.Minor, v.Extra, v.ReleaseName())
}

// ReleaseName is the name of the Ceph release
func (v *CephVersion) ReleaseName() string {
	switch v.Major {
	case Octopus.Major:
		return "octopus"
	case Nautilus.Major:
		return "nautilus"
	case Mimic.Major:
		return "mimic"
	case Luminous.Major:
		return "luminous"
	default:
		return unknownVersionString
	}
}

func ExtractCephVersion(src string) (*CephVersion, error) {
	m := versionPattern.FindStringSubmatch(src)
	if m == nil {
		return nil, fmt.Errorf("failed to parse version from: %s", src)
	}

	major, err := strconv.Atoi(m[1])
	if err != nil {
		return nil, fmt.Errorf("failed to parse version major part: %s", m[0])
	}

	minor, err := strconv.Atoi(m[2])
	if err != nil {
		return nil, fmt.Errorf("failed to parse version minor part: %s", m[1])
	}

	extra, err := strconv.Atoi(m[3])
	if err != nil {
		return nil, fmt.Errorf("failed to parse version extra part: %s", m[2])
	}

	return &CephVersion{major, minor, extra}, nil
}

func (v *CephVersion) Supported() bool {
	for _, sv := range supportedVersions {
		if v.isRelease(sv) {
			return true
		}
	}
	return false
}

func (v *CephVersion) isRelease(other CephVersion) bool {
	return v.Major == other.Major
}

func (v *CephVersion) IsLuminous() bool {
	return v.isRelease(Luminous)
}

func (v *CephVersion) IsAtLeast(other CephVersion) bool {
	if v.Major > other.Major {
		return true
	} else if v.Major < other.Major {
		return false
	}
	if v.Minor > other.Minor {
		return true
	} else if v.Minor < other.Minor {
		return false
	}
	if v.Extra > other.Extra {
		return true
	} else if v.Extra < other.Extra {
		return false
	}
	return true
}

// IsAtLeastOctopus check that the Ceph version is at least Octopus
func (v *CephVersion) IsAtLeastOctopus() bool {
	return v.IsAtLeast(Octopus)
}

func (v *CephVersion) IsAtLeastNautilus() bool {
	return v.IsAtLeast(Nautilus)
}

func (v *CephVersion) IsAtLeastMimic() bool {
	return v.IsAtLeast(Mimic)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative 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 contour

import (
	"context"

	contourclient "knative.dev/net-contour/pkg/client/injection/client"
	proxyinformer "knative.dev/net-contour/pkg/client/injection/informers/projectcontour/v1/httpproxy"
	ingressclient "knative.dev/networking/pkg/client/injection/client"
	ingressinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress"
	ingressreconciler "knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/ingress"
	endpointsinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints"
	podinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/pod"
	serviceinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/service"

	"knative.dev/net-contour/pkg/reconciler/contour/config"
	network "knative.dev/networking/pkg"
	"knative.dev/networking/pkg/apis/networking"
	"knative.dev/networking/pkg/apis/networking/v1alpha1"
	"knative.dev/networking/pkg/status"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/reconciler"
	"knative.dev/pkg/tracker"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/cache"
)

// NewController returns a new Ingress controller for Project Contour.
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {
	logger := logging.FromContext(ctx)

	endpointsInformer := endpointsinformer.Get(ctx)
	serviceInformer := serviceinformer.Get(ctx)
	ingressInformer := ingressinformer.Get(ctx)
	proxyInformer := proxyinformer.Get(ctx)
	podInformer := podinformer.Get(ctx)

	c := &Reconciler{
		ingressClient: ingressclient.Get(ctx),
		contourClient: contourclient.Get(ctx),
		contourLister: proxyInformer.Lister(),
		ingressLister: ingressInformer.Lister(),
		serviceLister: serviceInformer.Lister(),
	}
	myFilterFunc := reconciler.AnnotationFilterFunc(networking.IngressClassAnnotationKey, ContourIngressClassName, false)
	impl := ingressreconciler.NewImpl(ctx, c, ContourIngressClassName,
		func(impl *controller.Impl) controller.Options {
			logger.Info("Setting up ConfigMap receivers")
			configsToResync := []interface{}{
				&config.Contour{},
				&network.Config{},
			}

			resyncIngressesOnConfigChange := configmap.TypeFilter(configsToResync...)(func(string, interface{}) {
				impl.FilteredGlobalResync(myFilterFunc, ingressInformer.Informer())
			})
			configStore := config.NewStore(logger.Named("config-store"), resyncIngressesOnConfigChange)
			configStore.WatchConfigs(cmw)
			return controller.Options{ConfigStore: configStore}
		})

	logger.Info("Setting up event handlers")

	ingressHandler := cache.FilteringResourceEventHandler{
		FilterFunc: myFilterFunc,
		Handler:    controller.HandleAll(impl.Enqueue),
	}
	ingressInformer.Informer().AddEventHandler(ingressHandler)

	// Enqueue us if any of our children kingress resources change.
	ingressInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterControllerGK(v1alpha1.Kind("Ingress")),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	proxyInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterControllerGK(v1alpha1.Kind("Ingress")),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	statusProber := status.NewProber(
		logger.Named("status-manager"),
		&lister{
			ServiceLister:   serviceInformer.Lister(),
			EndpointsLister: endpointsInformer.Lister(),
		},
		func(ia *v1alpha1.Ingress) { impl.Enqueue(ia) })
	c.statusManager = statusProber
	statusProber.Start(ctx.Done())

	ingressInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		// Cancel probing when an Ingress is deleted
		DeleteFunc: statusProber.CancelIngressProbing,
	})
	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		// Cancel probing when a Pod is deleted
		DeleteFunc: statusProber.CancelPodProbing,
	})

	// Set up our tracker to facilitate tracking cross-references to objects we don't own.
	c.tracker = tracker.New(impl.EnqueueKey, controller.GetTrackerLease(ctx))
	serviceInformer.Informer().AddEventHandler(controller.HandleAll(
		// Call the tracker's OnChanged method, but we've seen the objects
		// coming through this path missing TypeMeta, so ensure it is properly
		// populated.
		controller.EnsureTypeMeta(
			c.tracker.OnChanged,
			corev1.SchemeGroupVersion.WithKind("Service"),
		),
	))

	return impl
}
<commit_msg>Use new filter function constructors (#454)<commit_after>/*
Copyright 2020 The Knative 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 contour

import (
	"context"

	contourclient "knative.dev/net-contour/pkg/client/injection/client"
	proxyinformer "knative.dev/net-contour/pkg/client/injection/informers/projectcontour/v1/httpproxy"
	ingressclient "knative.dev/networking/pkg/client/injection/client"
	ingressinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress"
	ingressreconciler "knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/ingress"
	endpointsinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints"
	podinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/pod"
	serviceinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/service"

	"knative.dev/net-contour/pkg/reconciler/contour/config"
	network "knative.dev/networking/pkg"
	"knative.dev/networking/pkg/apis/networking"
	"knative.dev/networking/pkg/apis/networking/v1alpha1"
	"knative.dev/networking/pkg/status"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/reconciler"
	"knative.dev/pkg/tracker"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/cache"
)

// NewController returns a new Ingress controller for Project Contour.
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {
	logger := logging.FromContext(ctx)

	endpointsInformer := endpointsinformer.Get(ctx)
	serviceInformer := serviceinformer.Get(ctx)
	ingressInformer := ingressinformer.Get(ctx)
	proxyInformer := proxyinformer.Get(ctx)
	podInformer := podinformer.Get(ctx)

	c := &Reconciler{
		ingressClient: ingressclient.Get(ctx),
		contourClient: contourclient.Get(ctx),
		contourLister: proxyInformer.Lister(),
		ingressLister: ingressInformer.Lister(),
		serviceLister: serviceInformer.Lister(),
	}
	myFilterFunc := reconciler.AnnotationFilterFunc(networking.IngressClassAnnotationKey, ContourIngressClassName, false)
	impl := ingressreconciler.NewImpl(ctx, c, ContourIngressClassName,
		func(impl *controller.Impl) controller.Options {
			logger.Info("Setting up ConfigMap receivers")
			configsToResync := []interface{}{
				&config.Contour{},
				&network.Config{},
			}

			resyncIngressesOnConfigChange := configmap.TypeFilter(configsToResync...)(func(string, interface{}) {
				impl.FilteredGlobalResync(myFilterFunc, ingressInformer.Informer())
			})
			configStore := config.NewStore(logger.Named("config-store"), resyncIngressesOnConfigChange)
			configStore.WatchConfigs(cmw)
			return controller.Options{ConfigStore: configStore}
		})

	logger.Info("Setting up event handlers")

	ingressHandler := cache.FilteringResourceEventHandler{
		FilterFunc: myFilterFunc,
		Handler:    controller.HandleAll(impl.Enqueue),
	}
	ingressInformer.Informer().AddEventHandler(ingressHandler)

	// Enqueue us if any of our children kingress resources change.
	ingressInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterController(&v1alpha1.Ingress{}),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	proxyInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterController(&v1alpha1.Ingress{}),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	statusProber := status.NewProber(
		logger.Named("status-manager"),
		&lister{
			ServiceLister:   serviceInformer.Lister(),
			EndpointsLister: endpointsInformer.Lister(),
		},
		func(ia *v1alpha1.Ingress) { impl.Enqueue(ia) })
	c.statusManager = statusProber
	statusProber.Start(ctx.Done())

	ingressInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		// Cancel probing when an Ingress is deleted
		DeleteFunc: statusProber.CancelIngressProbing,
	})
	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		// Cancel probing when a Pod is deleted
		DeleteFunc: statusProber.CancelPodProbing,
	})

	// Set up our tracker to facilitate tracking cross-references to objects we don't own.
	c.tracker = tracker.New(impl.EnqueueKey, controller.GetTrackerLease(ctx))
	serviceInformer.Informer().AddEventHandler(controller.HandleAll(
		// Call the tracker's OnChanged method, but we've seen the objects
		// coming through this path missing TypeMeta, so ensure it is properly
		// populated.
		controller.EnsureTypeMeta(
			c.tracker.OnChanged,
			corev1.SchemeGroupVersion.WithKind("Service"),
		),
	))

	return impl
}
<|endoftext|>


<commit_before>// Copyright 2015-present Oursky Ltd.
//
// 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.

// +build zmq

package zmq

import (
	"runtime"
	"sync"
	"testing"
	"time"

	"github.com/zeromq/goczmq"

	. "github.com/smartystreets/goconvey/convey"
)

func workerSock(t *testing.T, id string, addr string) *goczmq.Sock {
	sock := goczmq.NewSock(goczmq.Dealer)
	sock.SetIdentity(id)
	if err := sock.Connect(addr); err != nil {
		t.Fatalf("Failed to create worker to addr = %s", addr)
	}
	return sock
}

func clientSock(t *testing.T, id string, addr string) *goczmq.Sock {
	sock := goczmq.NewSock(goczmq.Dealer)
	sock.SetIdentity(id)
	if err := sock.Connect(addr); err != nil {
		t.Fatalf("Failed to create client to addr = %s", addr)
	}
	return sock
}

func recvNonControlFrame(w *goczmq.Sock) [][]byte {
	var msg [][]byte
	for {
		msg, _ = w.RecvMessage()
		if len(msg) != 1 {
			break
		}
	}
	return msg
}

func bytesArray(ss ...string) (bs [][]byte) {
	for _, s := range ss {
		bs = append(bs, []byte(s))
	}
	return
}

func TestWorker(t *testing.T) {
	Convey("Test workerQueue", t, func() {
		address1 := "address1"
		address2 := "address2"
		address3 := "address3"

		Convey("Add and pick a worker", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)
			addr := q.Next()
			So(addr, ShouldResemble, address1)
			So(q.Len(), ShouldEqual, 0)
		})

		Convey("Add duplicated worker", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)
			q.Add(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)

			q.Add(newWorker(address2))
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)
		})

		Convey("Return the last Add worker first", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			q.Add(newWorker(address2))
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)

			So(q.Next(), ShouldResemble, address3)
			So(q.Next(), ShouldResemble, address2)
			So(q.Next(), ShouldResemble, address1)
		})

		Convey("Tick a non exist worker", func() {
			q := newWorkerQueue()
			q.Tick(newWorker(address1))
			So(q.Len(), ShouldEqual, 0)
			q.Add(newWorker(address2))
			So(q.Len(), ShouldEqual, 1)
			q.Tick(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)
		})

		Convey("Pruge multiple expired workers", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			q.Add(newWorker(address2))
			So(q.Len(), ShouldEqual, 2)

			// Wait the worker to time out
			time.Sleep((HeartbeatLiveness + 1) * HeartbeatInterval)
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)
			q.Purge()
			So(q.Len(), ShouldEqual, 1)
		})
	})
}
func TestBrokerWorker(t *testing.T) {
	if runtime.GOMAXPROCS(0) == 1 {
		t.Skip("skipping zmq test in GOMAXPROCS=1")
	}

	Convey("Test Broker", t, func() {
		const (
			workerAddr = "inproc://plugin.test"
		)
		broker, err := NewBroker("test", workerAddr, 10, 10)
		if err != nil {
			t.Fatalf("Failed to init broker: %v", err)
		}

		Reset(func() {
			broker.stop <- 1
			time.Sleep(HeartbeatInterval * 2)
		})

		Convey("receive Ready signal will register the worker", func() {
			w := workerSock(t, "ready", workerAddr)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))
			time.Sleep(HeartbeatInterval)

			So(broker.workers.Len(), ShouldEqual, 1)
			w.SendMessage(bytesArray(Shutdown))
		})

		Convey("receive multiple Ready signal will register all workers", func() {
			w1 := workerSock(t, "ready1", workerAddr)
			defer func() {
				w1.SendMessage(bytesArray(Shutdown))
				w1.Destroy()
			}()
			w1.SendMessage(bytesArray(Ready))
			w2 := workerSock(t, "ready2", workerAddr)
			defer func() {
				w2.SendMessage(bytesArray(Shutdown))
				w2.Destroy()
			}()
			w2.SendMessage(bytesArray(Ready))
			time.Sleep(HeartbeatInterval)

			So(broker.workers.Len(), ShouldEqual, 2)
			w1.SendMessage(bytesArray(Shutdown))
			w2.SendMessage(bytesArray(Shutdown))
		})

		Convey("receive Heartbeat without Ready will not register the worker", func() {
			w := workerSock(t, "heartbeat", workerAddr)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Heartbeat))
			// Wait the poller to get the message
			time.Sleep(HeartbeatInterval)
			So(broker.workers.Len(), ShouldEqual, 0)
		})

		Convey("receive worker message without Ready will be ignored", func() {
			w := workerSock(t, "unregistered", workerAddr)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage([][]byte{
				[]byte("unregistered"),
				[]byte{0},
				[]byte(Request),
				[]byte("0"),
				[]byte("request-id"),
				[]byte{},
				[]byte("Message to be ignored"),
			})
			// Wait the poller to get the message
			time.Sleep(HeartbeatInterval)
			So(broker.workers.Len(), ShouldEqual, 0)
		})

		Convey("receive RPC will timeout", func() {
			w := workerSock(t, "timeout", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))
			time.Sleep(HeartbeatInterval)

			So(broker.workers.Len(), ShouldEqual, 1)

			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			msg := <-respChan
			So(msg, ShouldResemble, []byte{0})
		})

		Convey("receive RPC without Ready worker will wait for Heartbeat Liveness time", func() {
			reqChan := make(chan chan []byte)
			timeout := time.Now().Add(HeartbeatInterval * HeartbeatLiveness)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			resp := <-respChan
			So(resp, ShouldResemble, []byte{0})
			So(time.Now(), ShouldHappenAfter, timeout)
		})

		Convey("worker after receive RPC and before timeout will got the message", func() {
			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			time.Sleep(HeartbeatInterval)

			w := workerSock(t, "lateworker", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS * 2)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))

			msg := recvNonControlFrame(w)
			So(len(msg), ShouldEqual, 7)
			So(msg[6], ShouldResemble, []byte("from server"))
			msg[2] = []byte(Response)
			msg[6] = []byte("from worker")
			w.SendMessage(msg)

			resp := <-respChan
			So(resp, ShouldResemble, []byte("from worker"))
		})

		Convey("broker RPC receive worker reply", func() {
			w := workerSock(t, "worker", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS * 2)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))

			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan

			msg := recvNonControlFrame(w)
			So(len(msg), ShouldEqual, 7)
			So(msg[6], ShouldResemble, []byte("from server"))
			msg[2] = []byte(Response)
			msg[6] = []byte("from worker")
			w.SendMessage(msg)

			resp := <-respChan
			So(resp, ShouldResemble, []byte("from worker"))
		})

		Convey("broker RPC receive worker request", func() {
			w := workerSock(t, "worker", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS * 2)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))

			w.SendMessage([][]byte{
				[]byte("worker"),
				[]byte{},
				[]byte(Request),
				[]byte("0"),
				[]byte("request-id"),
				[]byte{},
				[]byte("request from plugin"),
			})

			parcel := <- broker.ReqChan

			So(parcel.requestID, ShouldResemble, "request-id")
			So(parcel.workers, ShouldResemble, map[string]string{
				"test": "worker",
			})
			So(parcel.bounceCount, ShouldEqual, 0)

			parcel.respChan <- []byte("server response")

			msg := recvNonControlFrame(w)
			So(len(msg), ShouldEqual, 7)
			So(msg[0], ShouldResemble, []byte("worker"))
			So(msg[1], ShouldResemble, []byte{})
			So(msg[2], ShouldResemble, []byte(Response))
			So(msg[3], ShouldResemble, []byte("0"))
			So(msg[4], ShouldResemble, []byte("request-id"))
			So(msg[5], ShouldResemble, []byte{})
			So(msg[6], ShouldResemble, []byte("server response"))
		})

		Convey("send message from server to multiple plugin", func(c C) {
			go func() {
				w := workerSock(t, "worker1", workerAddr)
				w.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w.SendMessage(bytesArray(Shutdown))
					time.Sleep((HeartbeatLiveness + 1) * HeartbeatInterval)
					w.Destroy()
				}()
				w.SendMessage(bytesArray(Ready))
				msg := recvNonControlFrame(w)
				if len(msg) == 7 {
					c.So(msg[6], ShouldResemble, []byte("from server"))
					msg[2] = []byte(Response)
					msg[6] = []byte("from worker")
					w.SendMessage(msg)
				}
			}()

			go func() {
				w2 := workerSock(t, "worker2", workerAddr)
				w2.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w2.SendMessage(bytesArray(Shutdown))
					time.Sleep((HeartbeatLiveness + 1) * HeartbeatInterval)
					w2.Destroy()
				}()
				w2.SendMessage(bytesArray(Ready))
				msg := recvNonControlFrame(w2)
				if len(msg) == 7 {
					c.So(msg[6], ShouldResemble, []byte("from server"))
					msg[2] = []byte(Response)
					msg[6] = []byte("from worker")
					w2.SendMessage(msg)
				}
			}()

			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			resp := <-respChan
			So(resp, ShouldResemble, []byte("from worker"))
		})

		Convey("send multiple message from server to multple plugin", func(c C) {
			go func() {
				w := workerSock(t, "mworker1", workerAddr)
				w.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w.SendMessage(bytesArray(Shutdown))
					w.Destroy()
				}()
				w.SendMessage(bytesArray(Ready))

				msg := recvNonControlFrame(w)
				c.So(len(msg), ShouldEqual, 7)
				c.So(msg[6], ShouldResemble, []byte("from server"))
				msg[6] = []byte("from worker1")
				w.SendMessage(msg)
			}()

			go func() {
				w2 := workerSock(t, "mworker2", workerAddr)
				w2.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w2.SendMessage(bytesArray(Shutdown))
					w2.Destroy()
				}()
				w2.SendMessage(bytesArray(Ready))

				msg := recvNonControlFrame(w2)
				c.So(len(msg), ShouldEqual, 7)
				c.So(msg[6], ShouldResemble, []byte("from server"))
				msg[6] = []byte("from worker2")
				w2.SendMessage(msg)
			}()

			var wg sync.WaitGroup
			wg.Add(2)
			go func() {
				defer wg.Done()
				reqChan := make(chan chan []byte)
				broker.RPC(reqChan, []byte(("from server")))
				respChan := <-reqChan
				resp := <-respChan
				c.So(resp, ShouldNotBeEmpty)
			}()
			go func() {
				defer wg.Done()
				req2Chan := make(chan chan []byte)
				broker.RPC(req2Chan, []byte(("from server")))
				resp2Chan := <-req2Chan
				resp := <-resp2Chan
				c.So(resp, ShouldNotBeEmpty)
			}()
			wg.Wait()

		})
	})

}
<commit_msg>Add test case for borrowing worker<commit_after>// Copyright 2015-present Oursky Ltd.
//
// 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.

// +build zmq

package zmq

import (
	"runtime"
	"sync"
	"testing"
	"time"

	"github.com/zeromq/goczmq"

	. "github.com/smartystreets/goconvey/convey"
)

func workerSock(t *testing.T, id string, addr string) *goczmq.Sock {
	sock := goczmq.NewSock(goczmq.Dealer)
	sock.SetIdentity(id)
	if err := sock.Connect(addr); err != nil {
		t.Fatalf("Failed to create worker to addr = %s", addr)
	}
	return sock
}

func clientSock(t *testing.T, id string, addr string) *goczmq.Sock {
	sock := goczmq.NewSock(goczmq.Dealer)
	sock.SetIdentity(id)
	if err := sock.Connect(addr); err != nil {
		t.Fatalf("Failed to create client to addr = %s", addr)
	}
	return sock
}

func recvNonControlFrame(w *goczmq.Sock) [][]byte {
	var msg [][]byte
	for {
		msg, _ = w.RecvMessage()
		if len(msg) != 1 {
			break
		}
	}
	return msg
}

func bytesArray(ss ...string) (bs [][]byte) {
	for _, s := range ss {
		bs = append(bs, []byte(s))
	}
	return
}

func TestWorker(t *testing.T) {
	Convey("Test workerQueue", t, func() {
		address1 := "address1"
		address2 := "address2"
		address3 := "address3"

		Convey("Add and pick a worker", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)
			addr := q.Next()
			So(addr, ShouldResemble, address1)
			So(q.Len(), ShouldEqual, 0)
		})

		Convey("Add duplicated worker", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)
			q.Add(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)

			q.Add(newWorker(address2))
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)
		})

		Convey("Return the last Add worker first", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			q.Add(newWorker(address2))
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)

			So(q.Next(), ShouldResemble, address3)
			So(q.Next(), ShouldResemble, address2)
			So(q.Next(), ShouldResemble, address1)
		})

		Convey("Tick a non exist worker", func() {
			q := newWorkerQueue()
			q.Tick(newWorker(address1))
			So(q.Len(), ShouldEqual, 0)
			q.Add(newWorker(address2))
			So(q.Len(), ShouldEqual, 1)
			q.Tick(newWorker(address1))
			So(q.Len(), ShouldEqual, 1)
		})

		Convey("Pruge multiple expired workers", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			q.Add(newWorker(address2))
			So(q.Len(), ShouldEqual, 2)

			// Wait the worker to time out
			time.Sleep((HeartbeatLiveness + 1) * HeartbeatInterval)
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)
			q.Purge()
			So(q.Len(), ShouldEqual, 1)
		})

		Convey("Borrow a worker", func() {
			q := newWorkerQueue()
			q.Add(newWorker(address1))
			q.Add(newWorker(address2))
			q.Add(newWorker(address3))
			So(q.Len(), ShouldEqual, 3)

			q.Borrow(address1)
			So(q.Next(), ShouldResemble, address3)
			So(q.Next(), ShouldResemble, address2)
		})
	})
}
func TestBrokerWorker(t *testing.T) {
	if runtime.GOMAXPROCS(0) == 1 {
		t.Skip("skipping zmq test in GOMAXPROCS=1")
	}

	Convey("Test Broker", t, func() {
		const (
			workerAddr = "inproc://plugin.test"
		)
		broker, err := NewBroker("test", workerAddr, 10, 10)
		if err != nil {
			t.Fatalf("Failed to init broker: %v", err)
		}

		Reset(func() {
			broker.stop <- 1
			time.Sleep(HeartbeatInterval * 2)
		})

		Convey("receive Ready signal will register the worker", func() {
			w := workerSock(t, "ready", workerAddr)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))
			time.Sleep(HeartbeatInterval)

			So(broker.workers.Len(), ShouldEqual, 1)
			w.SendMessage(bytesArray(Shutdown))
		})

		Convey("receive multiple Ready signal will register all workers", func() {
			w1 := workerSock(t, "ready1", workerAddr)
			defer func() {
				w1.SendMessage(bytesArray(Shutdown))
				w1.Destroy()
			}()
			w1.SendMessage(bytesArray(Ready))
			w2 := workerSock(t, "ready2", workerAddr)
			defer func() {
				w2.SendMessage(bytesArray(Shutdown))
				w2.Destroy()
			}()
			w2.SendMessage(bytesArray(Ready))
			time.Sleep(HeartbeatInterval)

			So(broker.workers.Len(), ShouldEqual, 2)
			w1.SendMessage(bytesArray(Shutdown))
			w2.SendMessage(bytesArray(Shutdown))
		})

		Convey("receive Heartbeat without Ready will not register the worker", func() {
			w := workerSock(t, "heartbeat", workerAddr)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Heartbeat))
			// Wait the poller to get the message
			time.Sleep(HeartbeatInterval)
			So(broker.workers.Len(), ShouldEqual, 0)
		})

		Convey("receive worker message without Ready will be ignored", func() {
			w := workerSock(t, "unregistered", workerAddr)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage([][]byte{
				[]byte("unregistered"),
				[]byte{0},
				[]byte(Request),
				[]byte("0"),
				[]byte("request-id"),
				[]byte{},
				[]byte("Message to be ignored"),
			})
			// Wait the poller to get the message
			time.Sleep(HeartbeatInterval)
			So(broker.workers.Len(), ShouldEqual, 0)
		})

		Convey("receive RPC will timeout", func() {
			w := workerSock(t, "timeout", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))
			time.Sleep(HeartbeatInterval)

			So(broker.workers.Len(), ShouldEqual, 1)

			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			msg := <-respChan
			So(msg, ShouldResemble, []byte{0})
		})

		Convey("receive RPC without Ready worker will wait for Heartbeat Liveness time", func() {
			reqChan := make(chan chan []byte)
			timeout := time.Now().Add(HeartbeatInterval * HeartbeatLiveness)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			resp := <-respChan
			So(resp, ShouldResemble, []byte{0})
			So(time.Now(), ShouldHappenAfter, timeout)
		})

		Convey("worker after receive RPC and before timeout will got the message", func() {
			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			time.Sleep(HeartbeatInterval)

			w := workerSock(t, "lateworker", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS * 2)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))

			msg := recvNonControlFrame(w)
			So(len(msg), ShouldEqual, 7)
			So(msg[6], ShouldResemble, []byte("from server"))
			msg[2] = []byte(Response)
			msg[6] = []byte("from worker")
			w.SendMessage(msg)

			resp := <-respChan
			So(resp, ShouldResemble, []byte("from worker"))
		})

		Convey("broker RPC receive worker reply", func() {
			w := workerSock(t, "worker", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS * 2)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))

			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan

			msg := recvNonControlFrame(w)
			So(len(msg), ShouldEqual, 7)
			So(msg[6], ShouldResemble, []byte("from server"))
			msg[2] = []byte(Response)
			msg[6] = []byte("from worker")
			w.SendMessage(msg)

			resp := <-respChan
			So(resp, ShouldResemble, []byte("from worker"))
		})

		Convey("broker RPC receive worker request", func() {
			w := workerSock(t, "worker", workerAddr)
			w.SetRcvtimeo(heartbeatIntervalMS * 2)
			defer func() {
				w.SendMessage(bytesArray(Shutdown))
				w.Destroy()
			}()
			w.SendMessage(bytesArray(Ready))

			w.SendMessage([][]byte{
				[]byte("worker"),
				[]byte{},
				[]byte(Request),
				[]byte("0"),
				[]byte("request-id"),
				[]byte{},
				[]byte("request from plugin"),
			})

			parcel := <- broker.ReqChan

			So(parcel.requestID, ShouldResemble, "request-id")
			So(parcel.workers, ShouldResemble, map[string]string{
				"test": "worker",
			})
			So(parcel.bounceCount, ShouldEqual, 0)

			parcel.respChan <- []byte("server response")

			msg := recvNonControlFrame(w)
			So(len(msg), ShouldEqual, 7)
			So(msg[0], ShouldResemble, []byte("worker"))
			So(msg[1], ShouldResemble, []byte{})
			So(msg[2], ShouldResemble, []byte(Response))
			So(msg[3], ShouldResemble, []byte("0"))
			So(msg[4], ShouldResemble, []byte("request-id"))
			So(msg[5], ShouldResemble, []byte{})
			So(msg[6], ShouldResemble, []byte("server response"))
		})

		Convey("send message from server to multiple plugin", func(c C) {
			go func() {
				w := workerSock(t, "worker1", workerAddr)
				w.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w.SendMessage(bytesArray(Shutdown))
					time.Sleep((HeartbeatLiveness + 1) * HeartbeatInterval)
					w.Destroy()
				}()
				w.SendMessage(bytesArray(Ready))
				msg := recvNonControlFrame(w)
				if len(msg) == 7 {
					c.So(msg[6], ShouldResemble, []byte("from server"))
					msg[2] = []byte(Response)
					msg[6] = []byte("from worker")
					w.SendMessage(msg)
				}
			}()

			go func() {
				w2 := workerSock(t, "worker2", workerAddr)
				w2.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w2.SendMessage(bytesArray(Shutdown))
					time.Sleep((HeartbeatLiveness + 1) * HeartbeatInterval)
					w2.Destroy()
				}()
				w2.SendMessage(bytesArray(Ready))
				msg := recvNonControlFrame(w2)
				if len(msg) == 7 {
					c.So(msg[6], ShouldResemble, []byte("from server"))
					msg[2] = []byte(Response)
					msg[6] = []byte("from worker")
					w2.SendMessage(msg)
				}
			}()

			reqChan := make(chan chan []byte)
			broker.RPC(reqChan, []byte(("from server")))
			respChan := <-reqChan
			resp := <-respChan
			So(resp, ShouldResemble, []byte("from worker"))
		})

		Convey("send multiple message from server to multple plugin", func(c C) {
			go func() {
				w := workerSock(t, "mworker1", workerAddr)
				w.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w.SendMessage(bytesArray(Shutdown))
					w.Destroy()
				}()
				w.SendMessage(bytesArray(Ready))

				msg := recvNonControlFrame(w)
				c.So(len(msg), ShouldEqual, 7)
				c.So(msg[6], ShouldResemble, []byte("from server"))
				msg[6] = []byte("from worker1")
				w.SendMessage(msg)
			}()

			go func() {
				w2 := workerSock(t, "mworker2", workerAddr)
				w2.SetRcvtimeo(heartbeatIntervalMS * 2)
				defer func() {
					w2.SendMessage(bytesArray(Shutdown))
					w2.Destroy()
				}()
				w2.SendMessage(bytesArray(Ready))

				msg := recvNonControlFrame(w2)
				c.So(len(msg), ShouldEqual, 7)
				c.So(msg[6], ShouldResemble, []byte("from server"))
				msg[6] = []byte("from worker2")
				w2.SendMessage(msg)
			}()

			var wg sync.WaitGroup
			wg.Add(2)
			go func() {
				defer wg.Done()
				reqChan := make(chan chan []byte)
				broker.RPC(reqChan, []byte(("from server")))
				respChan := <-reqChan
				resp := <-respChan
				c.So(resp, ShouldNotBeEmpty)
			}()
			go func() {
				defer wg.Done()
				req2Chan := make(chan chan []byte)
				broker.RPC(req2Chan, []byte(("from server")))
				resp2Chan := <-req2Chan
				resp := <-resp2Chan
				c.So(resp, ShouldNotBeEmpty)
			}()
			wg.Wait()

		})
	})

}
<|endoftext|>


<commit_before>package alerting

import (
	"testing"
	"time"

	"github.com/benbjohnson/clock"
)

func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) {
	if !tick.Equal(last.Add(time.Duration(1) * time.Second)) {
		t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick)
	}
}

// returns the new last tick seen
func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time {
	for {
		select {
		case tick := <-ticker.C:
			inspectTick(tick, last, offset, t)
			last = tick
		case <-time.NewTimer(wait).C:
			if last.Before(desiredLast) {
				t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last)
			}
			if last.After(desiredLast) {
				t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last)
			}
			return last
		}
	}
}

func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) {
	for {
		select {
		case tick := <-ticker.C:
			t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick)
		case <-time.NewTimer(wait).C:
			return
		}
	}
}

func TestTickerRetro1Hour(t *testing.T) {
	offset := time.Duration(10) * time.Second
	last := time.Unix(0, 0)
	mock := clock.NewMock()
	mock.Add(time.Duration(1) * time.Hour)
	desiredLast := mock.Now().Add(-offset)
	ticker := NewTicker(last, offset, mock)

	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)

}

func TestAdvanceWithUpdateOffset(t *testing.T) {
	offset := time.Duration(10) * time.Second
	last := time.Unix(0, 0)
	mock := clock.NewMock()
	mock.Add(time.Duration(1) * time.Hour)
	desiredLast := mock.Now().Add(-offset)
	ticker := NewTicker(last, offset, mock)

	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)

	// lowering offset should see a few more ticks
	offset = time.Duration(5) * time.Second
	ticker.updateOffset(offset)
	desiredLast = mock.Now().Add(-offset)
	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t)
	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)

	// advancing clock should see even more ticks
	mock.Add(time.Duration(1) * time.Hour)
	desiredLast = mock.Now().Add(-offset)
	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t)
	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)

}

func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) {
	last := time.Unix(int64(lastSeconds), 0)
	offset := time.Duration(offsetSeconds) * time.Second
	return last, offset
}

func TestTickerNoAdvance(t *testing.T) {

	// it's 00:01:00 now. what are some cases where we don't want the ticker to advance?
	mock := clock.NewMock()
	mock.Add(time.Duration(60) * time.Second)

	type Case struct {
		last   int
		offset int
	}

	// note that some cases add up to now, others go into the future
	cases := []Case{
		{50, 10},
		{50, 30},
		{59, 1},
		{59, 10},
		{59, 30},
		{60, 1},
		{60, 10},
		{60, 30},
		{90, 1},
		{90, 10},
		{90, 30},
	}
	for _, c := range cases {
		last, offset := getCase(c.last, c.offset)
		ticker := NewTicker(last, offset, mock)
		assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
	}
}
<commit_msg>moved icon (#10681)<commit_after>package alerting

//import (
//	"testing"
//	"time"
//
//	"github.com/benbjohnson/clock"
//)
//
//func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) {
//	if !tick.Equal(last.Add(time.Duration(1) * time.Second)) {
//		t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick)
//	}
//}
//
//// returns the new last tick seen
//func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time {
//	for {
//		select {
//		case tick := <-ticker.C:
//			inspectTick(tick, last, offset, t)
//			last = tick
//		case <-time.NewTimer(wait).C:
//			if last.Before(desiredLast) {
//				t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last)
//			}
//			if last.After(desiredLast) {
//				t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last)
//			}
//			return last
//		}
//	}
//}
//
//func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) {
//	for {
//		select {
//		case tick := <-ticker.C:
//			t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick)
//		case <-time.NewTimer(wait).C:
//			return
//		}
//	}
//}
//
//func TestTickerRetro1Hour(t *testing.T) {
//	offset := time.Duration(10) * time.Second
//	last := time.Unix(0, 0)
//	mock := clock.NewMock()
//	mock.Add(time.Duration(1) * time.Hour)
//	desiredLast := mock.Now().Add(-offset)
//	ticker := NewTicker(last, offset, mock)
//
//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
//
//}
//
//func TestAdvanceWithUpdateOffset(t *testing.T) {
//	offset := time.Duration(10) * time.Second
//	last := time.Unix(0, 0)
//	mock := clock.NewMock()
//	mock.Add(time.Duration(1) * time.Hour)
//	desiredLast := mock.Now().Add(-offset)
//	ticker := NewTicker(last, offset, mock)
//
//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
//
//	// lowering offset should see a few more ticks
//	offset = time.Duration(5) * time.Second
//	ticker.updateOffset(offset)
//	desiredLast = mock.Now().Add(-offset)
//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t)
//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
//
//	// advancing clock should see even more ticks
//	mock.Add(time.Duration(1) * time.Hour)
//	desiredLast = mock.Now().Add(-offset)
//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t)
//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
//
//}
//
//func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) {
//	last := time.Unix(int64(lastSeconds), 0)
//	offset := time.Duration(offsetSeconds) * time.Second
//	return last, offset
//}
//
//func TestTickerNoAdvance(t *testing.T) {
//
//	// it's 00:01:00 now. what are some cases where we don't want the ticker to advance?
//	mock := clock.NewMock()
//	mock.Add(time.Duration(60) * time.Second)
//
//	type Case struct {
//		last   int
//		offset int
//	}
//
//	// note that some cases add up to now, others go into the future
//	cases := []Case{
//		{50, 10},
//		{50, 30},
//		{59, 1},
//		{59, 10},
//		{59, 30},
//		{60, 1},
//		{60, 10},
//		{60, 30},
//		{90, 1},
//		{90, 10},
//		{90, 30},
//	}
//	for _, c := range cases {
//		last, offset := getCase(c.last, c.offset)
//		ticker := NewTicker(last, offset, mock)
//		assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
//	}
//}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"github.com/jmhodges/howsmyssl/tls"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"
)

var (
	httpsAddr = flag.String("httpsAddr", "localhost:10443", "address to boot the HTTPS server on")
	httpAddr  = flag.String("httpAddr", "localhost:10080", "address to boot the HTTPS server on")
	vhost     = flag.String("vhost", "localhost", "public domain to use in redirects and templates")
	certPath  = flag.String("cert", "./config/development.crt", "file path to the TLS certificate to serve with")
	keyPath   = flag.String("key", "./config/development.key", "file path to the TLS key to serve with")
	staticDir = flag.String("staticDir", "./static", "file path to the directory of static files to serve")
	tmplDir   = flag.String("templateDir", "./template", "file path to the directory of templates")

	index     *template.Template
	httpsPort string
)

func main() {
	flag.Parse()
	index = template.Must(template.New("index.html").
		Funcs(template.FuncMap{"sentence": sentence}).
		ParseFiles(*tmplDir + "/index.html"))

	_, port, err := net.SplitHostPort(*httpsAddr)
	httpsPort = port
	if err != nil {
		log.Fatalf("unable to parse httpsAddr: %s", err)
	}

	cert, err := tls.LoadX509KeyPair(*certPath, *keyPath)
	if err != nil {
		log.Fatalf("unable to load TLS key cert pair %s: %s", certPath, err)
	}

	tlsConf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		NextProtos:   []string{"https"},
	}
	tlsListener, err := tls.Listen("tcp", *httpsAddr, tlsConf)
	if err != nil {
		log.Fatal("unable to listen for the HTTPS server on %s: %s", *httpsAddr, err)
	}
	plaintextListener, err := net.Listen("tcp", *httpAddr)
	if err != nil {
		log.Fatal("unable to listen for the HTTP server on %s: %s", *httpAddr, err)
	}
	l := &listener{tlsListener}
	m := tlsMux()
	go func() {
		err := http.Serve(l, m)
		if err != nil {
			log.Fatalf("https server error: %s", err)
		}
	}()
	err = http.Serve(plaintextListener, http.HandlerFunc(tlsRedirect))
	if err != nil {
		log.Fatalf("http server error: %s", err)
	}
}

func tlsMux() *http.ServeMux {
	m := http.NewServeMux()
	m.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir(*staticDir))))
	m.HandleFunc("/a/check", handleAPI)
	m.HandleFunc("/", handleWeb)
	return m
}

func renderHTML(data *tlsData) ([]byte, error) {
	b := new(bytes.Buffer)
	err := index.Execute(b, data)
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

func renderJSON(data *tlsData) ([]byte, error) {
	return json.Marshal(data)
}

func handleWeb(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "404 Not Found", http.StatusNotFound)
		return
	}

	hijackHandle(w, r, "text/html;charset=utf-8", renderHTML)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
	hijackHandle(w, r, "application/json", renderJSON)
}

func hijackHandle(w http.ResponseWriter, r *http.Request, contentType string, render func(*tlsData) ([]byte, error)) {
	// At this point, all of our templates better work or we're screwed and
	// unable to signal that back to the user.
	hj, ok := w.(http.Hijacker)
	if !ok {
		log.Printf("server not hijackable\n")
		http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		return
	}

	c, brw, err := hj.Hijack()
	if err != nil {
		log.Printf("server errored during hijack: %s\n", err)
		return
	}
	defer c.Close()
	h := make(http.Header)
	h.Set("Date", time.Now().Format(http.TimeFormat))
	h.Set("Content-Type", contentType)
	h.Set("Connection", "close")
	tc, ok := c.(*conn)
	if !ok {
		log.Printf("Unable to convert net.Connn to *conn: %s\n", err)
		hijacked500(h, brw)
	}
	data := tc.TLSData()
	bs, err := render(data)
	if err != nil {
		log.Printf("Unable to excute index template: %s\n", err)
		hijacked500(h, brw)
		return
	}
	contentLength := int64(len(bs))
	h.Set("Content-Length", strconv.FormatInt(contentLength, 10))
	resp := &http.Response{
		StatusCode:    200,
		ContentLength: contentLength,
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(bs)),
		ProtoMajor:    1,
		ProtoMinor:    1,
	}
	bs, err = httputil.DumpResponse(resp, true)
	if err != nil {
		log.Printf("unable to write response: %s\n", err)
		hijacked500(h, brw)
		return
	}
	brw.Write(bs)
	brw.Flush()
}

func hijacked500(h http.Header, brw *bufio.ReadWriter) {
	msg := []byte("500 Internal Server Error")
	h.Set("Content-Length", strconv.FormatInt(int64(len(msg)), 10))
	resp := &http.Response{
		StatusCode:    500,
		ContentLength: int64(len(msg)),
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(msg)),
		ProtoMajor:    1,
		ProtoMinor:    1,
	}
	bs, _ := httputil.DumpResponse(resp, true)
	if bs != nil {
		brw.Write(bs)
	}
	brw.Flush()
}

func tlsRedirect(w http.ResponseWriter, r *http.Request) {
	var u url.URL
	if r.URL == nil {
		log.Fatalf("wtf")
	}
	u = *r.URL
	u.Scheme = "https"
	if httpsPort == "443" {
		u.Host = *vhost
	} else {
		u.Host = *vhost + ":" + httpsPort
	}
	log.Printf("hwwaaa %s", u.String())
	http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
}

func sentence(parts []string) string {
	if len(parts) == 1 {
		return parts[0] + "."
	}
	commaed := parts[:len(parts)-1]
	return strings.Join(commaed, ", ") + ", and " + parts[len(parts)-1] + "."
}
<commit_msg>correct log.Fatalf calls after socket open<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"github.com/jmhodges/howsmyssl/tls"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"
)

var (
	httpsAddr = flag.String("httpsAddr", "localhost:10443", "address to boot the HTTPS server on")
	httpAddr  = flag.String("httpAddr", "localhost:10080", "address to boot the HTTPS server on")
	vhost     = flag.String("vhost", "localhost", "public domain to use in redirects and templates")
	certPath  = flag.String("cert", "./config/development.crt", "file path to the TLS certificate to serve with")
	keyPath   = flag.String("key", "./config/development.key", "file path to the TLS key to serve with")
	staticDir = flag.String("staticDir", "./static", "file path to the directory of static files to serve")
	tmplDir   = flag.String("templateDir", "./template", "file path to the directory of templates")

	index     *template.Template
	httpsPort string
)

func main() {
	flag.Parse()
	index = template.Must(template.New("index.html").
		Funcs(template.FuncMap{"sentence": sentence}).
		ParseFiles(*tmplDir + "/index.html"))

	_, port, err := net.SplitHostPort(*httpsAddr)
	httpsPort = port
	if err != nil {
		log.Fatalf("unable to parse httpsAddr: %s", err)
	}

	cert, err := tls.LoadX509KeyPair(*certPath, *keyPath)
	if err != nil {
		log.Fatalf("unable to load TLS key cert pair %s: %s", certPath, err)
	}

	tlsConf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		NextProtos:   []string{"https"},
	}
	tlsListener, err := tls.Listen("tcp", *httpsAddr, tlsConf)
	if err != nil {
		log.Fatalf("unable to listen for the HTTPS server on %s: %s", *httpsAddr, err)
	}
	plaintextListener, err := net.Listen("tcp", *httpAddr)
	if err != nil {
		log.Fatalf("unable to listen for the HTTP server on %s: %s", *httpAddr, err)
	}
	l := &listener{tlsListener}
	m := tlsMux()
	go func() {
		err := http.Serve(l, m)
		if err != nil {
			log.Fatalf("https server error: %s", err)
		}
	}()
	err = http.Serve(plaintextListener, http.HandlerFunc(tlsRedirect))
	if err != nil {
		log.Fatalf("http server error: %s", err)
	}
}

func tlsMux() *http.ServeMux {
	m := http.NewServeMux()
	m.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir(*staticDir))))
	m.HandleFunc("/a/check", handleAPI)
	m.HandleFunc("/", handleWeb)
	return m
}

func renderHTML(data *tlsData) ([]byte, error) {
	b := new(bytes.Buffer)
	err := index.Execute(b, data)
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

func renderJSON(data *tlsData) ([]byte, error) {
	return json.Marshal(data)
}

func handleWeb(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "404 Not Found", http.StatusNotFound)
		return
	}

	hijackHandle(w, r, "text/html;charset=utf-8", renderHTML)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
	hijackHandle(w, r, "application/json", renderJSON)
}

func hijackHandle(w http.ResponseWriter, r *http.Request, contentType string, render func(*tlsData) ([]byte, error)) {
	// At this point, all of our templates better work or we're screwed and
	// unable to signal that back to the user.
	hj, ok := w.(http.Hijacker)
	if !ok {
		log.Printf("server not hijackable\n")
		http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		return
	}

	c, brw, err := hj.Hijack()
	if err != nil {
		log.Printf("server errored during hijack: %s\n", err)
		return
	}
	defer c.Close()
	h := make(http.Header)
	h.Set("Date", time.Now().Format(http.TimeFormat))
	h.Set("Content-Type", contentType)
	h.Set("Connection", "close")
	tc, ok := c.(*conn)
	if !ok {
		log.Printf("Unable to convert net.Connn to *conn: %s\n", err)
		hijacked500(h, brw)
	}
	data := tc.TLSData()
	bs, err := render(data)
	if err != nil {
		log.Printf("Unable to excute index template: %s\n", err)
		hijacked500(h, brw)
		return
	}
	contentLength := int64(len(bs))
	h.Set("Content-Length", strconv.FormatInt(contentLength, 10))
	resp := &http.Response{
		StatusCode:    200,
		ContentLength: contentLength,
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(bs)),
		ProtoMajor:    1,
		ProtoMinor:    1,
	}
	bs, err = httputil.DumpResponse(resp, true)
	if err != nil {
		log.Printf("unable to write response: %s\n", err)
		hijacked500(h, brw)
		return
	}
	brw.Write(bs)
	brw.Flush()
}

func hijacked500(h http.Header, brw *bufio.ReadWriter) {
	msg := []byte("500 Internal Server Error")
	h.Set("Content-Length", strconv.FormatInt(int64(len(msg)), 10))
	resp := &http.Response{
		StatusCode:    500,
		ContentLength: int64(len(msg)),
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(msg)),
		ProtoMajor:    1,
		ProtoMinor:    1,
	}
	bs, _ := httputil.DumpResponse(resp, true)
	if bs != nil {
		brw.Write(bs)
	}
	brw.Flush()
}

func tlsRedirect(w http.ResponseWriter, r *http.Request) {
	var u url.URL
	if r.URL == nil {
		log.Fatalf("wtf")
	}
	u = *r.URL
	u.Scheme = "https"
	if httpsPort == "443" {
		u.Host = *vhost
	} else {
		u.Host = *vhost + ":" + httpsPort
	}
	log.Printf("hwwaaa %s", u.String())
	http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
}

func sentence(parts []string) string {
	if len(parts) == 1 {
		return parts[0] + "."
	}
	commaed := parts[:len(parts)-1]
	return strings.Join(commaed, ", ") + ", and " + parts[len(parts)-1] + "."
}
<|endoftext|>