<commit_before>package statecmd_test

import (
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api/params"
	"launchpad.net/juju-core/state/statecmd"
	coretesting "launchpad.net/juju-core/testing"
	"os"
)

type DeploySuite struct {
	testing.RepoSuite
}

var _ = Suite(&DeploySuite{})

func (s *DeploySuite) runDeploy(c *C, args params.ServiceDeploy) error {
	c.Logf("Running deploy: %+v", args)
	conf, err := s.State.EnvironConfig()
	c.Assert(err, IsNil)
	curl, err := charm.InferURL(args.CharmUrl, conf.DefaultSeries())
	c.Assert(err, IsNil)
	repo, err := charm.InferRepository(curl, os.Getenv("JUJU_REPOSITORY"))
	c.Assert(err, IsNil)
	return statecmd.ServiceDeploy(s.State, args, s.Conn, curl, repo)
}

func (s *DeploySuite) TestCharmDir(c *C) {
	coretesting.Charms.ClonedDirPath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		NumUnits: 1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	// Note that this tests the automatic creation of a service name (dummy)
	// from the charm URL.  This functionality will be going away soon as
	// ServiceName becomes a required argument.
	s.AssertService(c, "dummy", curl, 1, 0)
}

func (s *DeploySuite) TestUpgradeCharmDir(c *C) {
	dirPath := coretesting.Charms.ClonedDirPath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:     "local:dummy",
		BumpRevision: true,
		NumUnits:     1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-2")
	s.AssertService(c, "dummy", curl, 1, 0)
	// Check the charm really was upgraded.
	ch, err := charm.ReadDir(dirPath)
	c.Assert(err, IsNil)
	c.Assert(ch.Revision(), Equals, 2)
}

func (s *DeploySuite) TestCharmBundle(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:    "local:dummy",
		ServiceName: "some-service-name",
		NumUnits:    1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	s.AssertService(c, "some-service-name", curl, 1, 0)
}

func (s *DeploySuite) TestForceMachine(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
	c.Assert(err, IsNil)
	args := params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: machine.Id(),
		NumUnits:       1,
	}
	err = s.runDeploy(c, args)
	c.Assert(err, IsNil)
	svc, err := s.State.Service("portlandia")
	c.Assert(err, IsNil)
	units, err := svc.AllUnits()
	c.Assert(err, IsNil)
	c.Assert(units, HasLen, 1)
	mid, err := units[0].AssignedMachineId()
	c.Assert(err, IsNil)
	c.Assert(mid, Equals, machine.Id())
}

func (s *DeploySuite) TestForceMachineInvalid(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: "42",
		NumUnits:       1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `cannot assign unit "portlandia/0" to machine: machine 42 not found`)

	args = params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: "abc",
		NumUnits:       1,
	}
	err = s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `invalid machine id "abc"`)

	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
	args = params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: machine.Id(),
		NumUnits:       5,
	}
	err = s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `force-machine cannot be used for multiple units`)

	coretesting.Charms.BundlePath(s.SeriesPath, "logging")
	args = params.ServiceDeploy{
		CharmUrl:       "local:logging",
		ForceMachineId: machine.Id(),
	}
	err = s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `subordinate service cannot specify force-machine`)
}

func (s *DeploySuite) TestCannotUpgradeCharmBundle(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:     "local:dummy",
		BumpRevision: true,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `cannot increment revision of charm "local:precise/dummy-1": not a directory`)
	// Verify state not touched...
	curl := charm.MustParseURL("local:precise/dummy-1")
	_, err = s.State.Charm(curl)
	c.Assert(err, ErrorMatches, `charm "local:precise/dummy-1" not found`)
	_, err = s.State.Service("dummy")
	c.Assert(err, ErrorMatches, `service "dummy" not found`)
}

func (s *DeploySuite) TestAddsPeerRelations(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "riak")
	args := params.ServiceDeploy{
		CharmUrl: "local:riak",
		NumUnits: 1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/riak-7")
	_, rels := s.AssertService(c, "riak", curl, 1, 1)
	rel := rels[0]
	ep, err := rel.Endpoint("riak")
	c.Assert(err, IsNil)
	c.Assert(ep.Name, Equals, "ring")
	c.Assert(ep.Role, Equals, charm.RolePeer)
	c.Assert(ep.Scope, Equals, charm.ScopeGlobal)
}

func (s *DeploySuite) TestNumUnits(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		NumUnits: 13,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	s.AssertService(c, "dummy", curl, 13, 0)
}

func (s *DeploySuite) TestNumUnitsZero(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		NumUnits: 0,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, "must deploy at least one unit")
}

func (s *DeploySuite) TestSubordinateCharm(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "logging")
	args := params.ServiceDeploy{
		CharmUrl: "local:logging",
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/logging-1")
	s.AssertService(c, "logging", curl, 0, 0)
}

func (s *DeploySuite) TestConstraints(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:    "local:dummy",
		Constraints: constraints.MustParse("mem=2G cpu-cores=2"),
		NumUnits:    1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	service, _ := s.AssertService(c, "dummy", curl, 1, 0)
	cons, err := service.Constraints()
	c.Assert(err, IsNil)
	c.Assert(cons, DeepEquals, constraints.MustParse("mem=2G cpu-cores=2"))
}

func (s *DeploySuite) TestSubordinateConstraints(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "logging")
	args := params.ServiceDeploy{
		CharmUrl:    "local:logging",
		Constraints: constraints.MustParse("mem=1G"),
	}
	err := s.runDeploy(c, args)
	c.Assert(err, Equals, state.ErrSubordinateConstraints)
}
<commit_msg>Testing config map<commit_after>package statecmd_test

import (
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api/params"
	"launchpad.net/juju-core/state/statecmd"
	coretesting "launchpad.net/juju-core/testing"
	"os"
)

type DeploySuite struct {
	testing.RepoSuite
}

var _ = Suite(&DeploySuite{})

func (s *DeploySuite) runDeploy(c *C, args params.ServiceDeploy) error {
	c.Logf("Running deploy: %+v", args)
	conf, err := s.State.EnvironConfig()
	c.Assert(err, IsNil)
	curl, err := charm.InferURL(args.CharmUrl, conf.DefaultSeries())
	c.Assert(err, IsNil)
	repo, err := charm.InferRepository(curl, os.Getenv("JUJU_REPOSITORY"))
	c.Assert(err, IsNil)
	return statecmd.ServiceDeploy(s.State, args, s.Conn, curl, repo)
}

func (s *DeploySuite) TestCharmDir(c *C) {
	coretesting.Charms.ClonedDirPath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		NumUnits: 1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	// Note that this tests the automatic creation of a service name (dummy)
	// from the charm URL.  This functionality will be going away soon as
	// ServiceName becomes a required argument.
	s.AssertService(c, "dummy", curl, 1, 0)
}

func (s *DeploySuite) TestUpgradeCharmDir(c *C) {
	dirPath := coretesting.Charms.ClonedDirPath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:     "local:dummy",
		BumpRevision: true,
		NumUnits:     1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-2")
	s.AssertService(c, "dummy", curl, 1, 0)
	// Check the charm really was upgraded.
	ch, err := charm.ReadDir(dirPath)
	c.Assert(err, IsNil)
	c.Assert(ch.Revision(), Equals, 2)
}

func (s *DeploySuite) TestCharmBundle(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:    "local:dummy",
		ServiceName: "some-service-name",
		NumUnits:    1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	s.AssertService(c, "some-service-name", curl, 1, 0)
}

func (s *DeploySuite) TestForceMachine(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
	c.Assert(err, IsNil)
	args := params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: machine.Id(),
		NumUnits:       1,
	}
	err = s.runDeploy(c, args)
	c.Assert(err, IsNil)
	svc, err := s.State.Service("portlandia")
	c.Assert(err, IsNil)
	units, err := svc.AllUnits()
	c.Assert(err, IsNil)
	c.Assert(units, HasLen, 1)
	mid, err := units[0].AssignedMachineId()
	c.Assert(err, IsNil)
	c.Assert(mid, Equals, machine.Id())
}

func (s *DeploySuite) TestForceMachineInvalid(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: "42",
		NumUnits:       1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `cannot assign unit "portlandia/0" to machine: machine 42 not found`)

	args = params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: "abc",
		NumUnits:       1,
	}
	err = s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `invalid machine id "abc"`)

	machine, err := s.State.AddMachine("precise", state.JobHostUnits)
	args = params.ServiceDeploy{
		CharmUrl:       "local:dummy",
		ServiceName:    "portlandia",
		ForceMachineId: machine.Id(),
		NumUnits:       5,
	}
	err = s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `force-machine cannot be used for multiple units`)

	coretesting.Charms.BundlePath(s.SeriesPath, "logging")
	args = params.ServiceDeploy{
		CharmUrl:       "local:logging",
		ForceMachineId: machine.Id(),
	}
	err = s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `subordinate service cannot specify force-machine`)
}

func (s *DeploySuite) TestCannotUpgradeCharmBundle(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:     "local:dummy",
		BumpRevision: true,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, `cannot increment revision of charm "local:precise/dummy-1": not a directory`)
	// Verify state not touched...
	curl := charm.MustParseURL("local:precise/dummy-1")
	_, err = s.State.Charm(curl)
	c.Assert(err, ErrorMatches, `charm "local:precise/dummy-1" not found`)
	_, err = s.State.Service("dummy")
	c.Assert(err, ErrorMatches, `service "dummy" not found`)
}

func (s *DeploySuite) TestAddsPeerRelations(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "riak")
	args := params.ServiceDeploy{
		CharmUrl: "local:riak",
		NumUnits: 1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/riak-7")
	_, rels := s.AssertService(c, "riak", curl, 1, 1)
	rel := rels[0]
	ep, err := rel.Endpoint("riak")
	c.Assert(err, IsNil)
	c.Assert(ep.Name, Equals, "ring")
	c.Assert(ep.Role, Equals, charm.RolePeer)
	c.Assert(ep.Scope, Equals, charm.ScopeGlobal)
}

func (s *DeploySuite) TestNumUnits(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		NumUnits: 13,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	s.AssertService(c, "dummy", curl, 13, 0)
}

func (s *DeploySuite) TestNumUnitsZero(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		NumUnits: 0,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, ErrorMatches, "must deploy at least one unit")
}

func (s *DeploySuite) TestSubordinateCharm(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "logging")
	args := params.ServiceDeploy{
		CharmUrl: "local:logging",
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/logging-1")
	s.AssertService(c, "logging", curl, 0, 0)
}

func (s *DeploySuite) TestConfigMap(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl: "local:dummy",
		Config: map[string]string{
			"skill-level": "1",
		},
		NumUnits: 1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	s.AssertService(c, "dummy", curl, 1, 0)
	svc, err := s.State.Service("dummy")
	c.Assert(err, IsNil)
	cfg, err := svc.Config()
	c.Assert(err, IsNil)
	skill, _ := cfg.Get("skill-level")
	c.Assert(skill, Equals, int64(1))
}

func (s *DeploySuite) TestConstraints(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "dummy")
	args := params.ServiceDeploy{
		CharmUrl:    "local:dummy",
		Constraints: constraints.MustParse("mem=2G cpu-cores=2"),
		NumUnits:    1,
	}
	err := s.runDeploy(c, args)
	c.Assert(err, IsNil)
	curl := charm.MustParseURL("local:precise/dummy-1")
	service, _ := s.AssertService(c, "dummy", curl, 1, 0)
	cons, err := service.Constraints()
	c.Assert(err, IsNil)
	c.Assert(cons, DeepEquals, constraints.MustParse("mem=2G cpu-cores=2"))
}

func (s *DeploySuite) TestSubordinateConstraints(c *C) {
	coretesting.Charms.BundlePath(s.SeriesPath, "logging")
	args := params.ServiceDeploy{
		CharmUrl:    "local:logging",
		Constraints: constraints.MustParse("mem=1G"),
	}
	err := s.runDeploy(c, args)
	c.Assert(err, Equals, state.ErrSubordinateConstraints)
}
<|endoftext|>


<commit_before>// SILVER - Service Wrapper
//
// Copyright (c) 2014 PaperCut Software http://www.papercut.com/
// Use of this source code is governed by an MIT or GPL Version 2 license.
// See the project's LICENSE file for more information.
//
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"sync"
	"time"

	"github.com/kardianos/service"

	"github.com/robfig/cron"

	"github.com/papercutsoftware/silver/lib/logging"
	"github.com/papercutsoftware/silver/lib/pathutils"
	"github.com/papercutsoftware/silver/service/cmdutil"
	"github.com/papercutsoftware/silver/service/config"
	"github.com/papercutsoftware/silver/service/svcutil"
)

const (
	defaultRefreshPoll = 10 * time.Second
)

type context struct {
	conf         *config.Config
	terminate    chan struct{}
	logger       *log.Logger
	runningGroup sync.WaitGroup
	cronManager  *cron.Cron
}

func main() {

	ctx := &context{
		terminate: make(chan struct{}),
	}

	// Parse config (we don't action any errors quite yet)
	var err error
	ctx.conf, err = loadConf()
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: Invalid config - %v\n", err)
		os.Exit(1)
	}

	action, actionArgs, err := parse(os.Args)
	if err != nil {
		printUsage(ctx.conf.ServiceDescription.DisplayName, ctx.conf.ServiceDescription.Description)
		os.Exit(1)
	}

	if err := os.Chdir(exeFolder()); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: Unable to set working directory - %v\n", err)
		os.Exit(1)
	}
	switch action {
	case "command":
		execCommand(ctx, actionArgs)
	case "validate":
		fmt.Println("Config is valid")
		os.Exit(0)
	default:
		osServiceControl(ctx)
	}
}

func osServiceControl(ctx *context) {
	// Setup log file out
	logFile := ctx.conf.ServiceConfig.LogFile
	maxSize := int64(ctx.conf.ServiceConfig.LogFileMaxSizeMb) * 1024 * 1024
	if logFile == "" {
		logFile = serviceName() + ".log"
	}
	ctx.logger = logging.NewFileLoggerWithMaxSize(logFile, maxSize)

	// Setup service
	svcConfig := &service.Config{
		Name:        serviceName(),
		DisplayName: ctx.conf.ServiceDescription.DisplayName,
		Description: ctx.conf.ServiceDescription.Description,
	}

	osService := &osService{ctx: ctx}
	svc, err := service.New(osService, svcConfig)
	if err != nil {
		fmt.Printf("ERROR: Invalid service config: %v\n", err)
		os.Exit(1)
	}

	if len(os.Args) > 1 && os.Args[1] != "run" {
		err = service.Control(svc, os.Args[1])
		if err != nil {
			fmt.Printf("ERROR: Invalid service command: %v\n", err)
			os.Exit(1)
		}
		os.Exit(0)
	}

	err = svc.Run()
	if err != nil {
		fmt.Printf("ERROR: %v\n", err)
		os.Exit(1)
	}

	pidFile := ctx.conf.ServiceConfig.PidFile
	if pidFile != "" {
		ioutil.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0644)
	}
}

func printUsage(svcDisplayName, svcDesc string) {
	fmt.Printf("%s (%s)\n", svcDisplayName,
		serviceName())
	fmt.Printf("%s\n\n", svcDesc)
	fmt.Printf("Usage:\n")
	fmt.Printf("%s [install|uninstall|start|stop|command|validate|run|help] [command-name]\n", exeName())
	fmt.Printf("  install   - Install the service.\n")
	fmt.Printf("  uninstall - Remove/uninstall the service.\n")
	fmt.Printf("  start     - Start an installed service.\n")
	fmt.Printf("  stop      - Stop an installed service.\n")
	fmt.Printf("  validate  - Test the configuration file.\n")
	fmt.Printf("  run       - Run service on in command-line mode.\n")
	fmt.Printf("  command   - Run a command [command-name].\n")
	fmt.Printf("  help      - This usage message.\n")
}

func loadConf() (conf *config.Config, err error) {
	// FIXME: Not Get this function out of utils.
	confPath := getConfigFilePath()
	vars := config.ReplacementVars{
		ServiceName: serviceName(),
		ServiceRoot: exeFolder(),
	}
	conf, err = config.LoadConfig(confPath, vars)
	if err != nil {
		return nil, err
	}

	// Merge in any include files
	for _, include := range conf.Include {
		conf, err = config.MergeInclude(*conf, include, vars)
		if err != nil {
			return nil, err
		}
	}
	return conf, err
}

func execCommand(ctx *context, args []string) {
	/*
	*  IMPORTANT:
	*  Don't write to any log files, etc.  Commands are not system service code.
	*  Commands should be thought of as a "symlink" style, and run under a different
	*  user context.
	*
	*  args format: 1st element is the command. Any extras are appended to the command.
	 */
	if len(ctx.conf.Commands) == 0 {
		fmt.Fprintf(os.Stderr, "There are no commands configured!\n")
		os.Exit(1)
	}

	var cmd *config.Command
	if len(args) > 0 {
		cmdName := args[0]
		for _, c := range ctx.conf.Commands {
			if c.Name == cmdName {
				cmd = &c
				break
			}
		}
	}

	if cmd == nil {
		// Print command usage
		fmt.Fprintf(os.Stderr, "Valid commands are:\n")
		for _, command := range ctx.conf.Commands {
			fmt.Fprintf(os.Stderr, "    %s\n", command.Name)
		}
		os.Exit(1)
	}

	cmdConf := cmdutil.CommandConfig{}
	cmdConf.Path = pathutils.FindLastFile(cmd.Path)
	// Append any extra commands
	cmdConf.Args = append(cmd.Args, args[1:]...)
	// FIXME: Maybe unit conversion should be in the config layer?
	cmdConf.ExecTimeout = (time.Second * time.Duration(cmd.TimeoutSecs))

	exitCode, err := cmdutil.Execute(cmdConf)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
	}
	os.Exit(exitCode)
}

type osService struct {
	ctx *context
}

func (o *osService) Start(s service.Service) error {
	msg := fmt.Sprintf("Service '%s' started.", serviceName())
	o.ctx.logger.Printf(msg)
	sysLogger, err := s.Logger(nil)
	if err != nil {
		sysLogger.Info(msg)
	}

	doStart(o.ctx)
	go watchForReload(o.ctx)

	return nil
}

func doStart(ctx *context) {
	sf := stopFileName(ctx)
	if sf != "" {
		os.Remove(sf)
	}
	execStartupTasks(ctx)
	setupScheduledTasks(ctx)
	startServices(ctx)
}

func (o *osService) Stop(s service.Service) error {
	o.ctx.logger.Printf(fmt.Sprintf("Stopping '%s' service...", serviceName()))

	doStop(o.ctx)

	pidFile := o.ctx.conf.ServiceConfig.PidFile
	if pidFile != "" {
		os.Remove(pidFile)
	}

	msg := fmt.Sprintf("Stopped '%s' service.", serviceName())
	o.ctx.logger.Printf(msg)

	sysLogger, err := s.Logger(nil)
	if err != nil {
		sysLogger.Info(msg)
	}
	return nil
}

func stopFileName(ctx *context) string {
	stopFile := ctx.conf.ServiceConfig.StopFile
	if stopFile != "disabled" {
		return ""
	}
	return stopFile
}

func doStop(ctx *context) {
	// Create stop file... another method to signal services to stop.
	sf := stopFileName(ctx)
	if sf != "" {
		ioutil.WriteFile(sf, nil, 0644)
		defer os.Remove(sf)
	}
	if ctx.cronManager != nil {
		ctx.cronManager.Stop()
		ctx.cronManager = nil
	}
	if ctx.terminate != nil {
		close(ctx.terminate)
	}
	ctx.runningGroup.Wait()
}

func watchForReload(ctx *context) {
	f := ctx.conf.ServiceConfig.ReloadFile
	for {
		// FIXME: File system notification rather than polling?
		time.Sleep(defaultRefreshPoll)
		if _, err := os.Stat(f); err == nil {
			if err := os.Remove(f); err == nil {
				ctx.logger.Printf("Reload requested")
				doStop(ctx)
				time.Sleep(time.Second)
				ctx.conf, _ = loadConf()
				doStart(ctx)
			}
		}
	}
}

func execStartupTasks(ctx *context) {
	ctx.logger.Printf("Starting %d startup tasks.", len(ctx.conf.StartupTasks))
	for _, task := range ctx.conf.StartupTasks {
		runTask := func(task config.StartupTask) {
			ctx.runningGroup.Add(1)
			defer ctx.runningGroup.Done()
			taskName := path.Base(task.Path)
			taskConfig := createTaskConfig(ctx, task.Task)
			if exitCode, err := svcutil.ExecuteTask(ctx.terminate, taskConfig); err != nil {
				ctx.logger.Printf("ERROR: Startup task '%s' reported: %v", taskName, err)
			} else {
				ctx.logger.Printf("Startup task '%s' finished with exit code %d", taskName, exitCode)
			}
		}
		if task.Async {
			go runTask(task)
		} else {
			if task.StartupDelaySecs > 0 || task.StartupRandomDelaySecs > 0 {
				ctx.logger.Printf("WARNING: Only Async startup tasks should have startup delays.")
			}
			runTask(task)
		}
	}
}

func startServices(ctx *context) {
	ctx.logger.Printf("Starting %d services.", len(ctx.conf.Services))
	for _, service := range ctx.conf.Services {
		go func(service config.Service) {
			ctx.runningGroup.Add(1)
			defer ctx.runningGroup.Done()
			serviceName := path.Base(service.Path)
			svcConfig := svcutil.ServiceConfig{}
			svcConfig.Path = pathutils.FindLastFile(service.Path)
			svcConfig.Args = service.Args
			svcConfig.GracefulShutDown = time.Duration(service.GracefulShutdownTimeoutSecs) * time.Second
			svcConfig.StartupDelay = time.Duration(service.StartupDelaySecs) * time.Second
			svcConfig.Logger = ctx.logger
			svcConfig.CrashConfig = svcutil.CrashConfig{
				MaxCountPerHour: service.MaxCrashCountPerHour,
				RestartDelay:    time.Duration(service.RestartDelaySecs) * time.Second,
			}
			if service.MonitorPing != nil {
				svcConfig.MonitorConfig = svcutil.MonitorConfig{
					URL:                   service.MonitorPing.URL,
					StartupDelay:          time.Duration(service.MonitorPing.StartupDelaySecs) * time.Second,
					Interval:              time.Duration(service.MonitorPing.IntervalSecs) * time.Second,
					Timeout:               time.Duration(service.MonitorPing.TimeoutSecs) * time.Second,
					RestartOnFailureCount: service.MonitorPing.RestartOnFailureCount,
				}
			}
			if err := svcutil.ExecuteService(ctx.terminate, svcConfig); err != nil {
				ctx.logger.Printf("ERROR: Service '%s' reported: %v", serviceName, err)
			}
		}(service)
	}
}

func createTaskConfig(ctx *context, task config.Task) svcutil.TaskConfig {
	taskConfig := svcutil.TaskConfig{}
	taskConfig.Path = pathutils.FindLastFile(task.Path)
	taskConfig.Args = task.Args
	taskConfig.ExecTimeout = time.Duration(task.TimeoutSecs) * time.Second
	taskConfig.StartupDelay = time.Duration(task.StartupDelaySecs) * time.Second
	taskConfig.StartupRandomDelay = time.Duration(task.StartupRandomDelaySecs) * time.Second
	taskConfig.Logger = ctx.logger
	return taskConfig
}

func setupScheduledTasks(ctx *context) {
	ctx.logger.Printf("Setting up %d scheduled tasks.", len(ctx.conf.ScheduledTasks))
	ctx.cronManager = cron.New()
	for _, scheduledTask := range ctx.conf.ScheduledTasks {
		taskConfig := createTaskConfig(ctx, scheduledTask.Task)
		runTask := func() {
			ctx.runningGroup.Add(1)
			defer ctx.runningGroup.Done()
			taskName := path.Base(taskConfig.Path)
			ctx.logger.Printf("Running schedule task '%s'", taskName)
			if exitCode, err := svcutil.ExecuteTask(ctx.terminate, taskConfig); err != nil {
				ctx.logger.Printf("ERROR: Scheduled task '%s' reported: %v", taskName, err)
			} else {
				ctx.logger.Printf("The task '%s' finished with exit code %d", taskName, exitCode)
			}
		}
		err := ctx.cronManager.AddFunc(scheduledTask.Schedule, runTask)
		if err != nil {
			ctx.logger.Printf("Unable to schedule task '%s': %v", scheduledTask.Path, err)
		}
	}
	ctx.cronManager.Start()
}
<commit_msg>Fixed .reload issue/bug<commit_after>// SILVER - Service Wrapper
//
// Copyright (c) 2014 PaperCut Software http://www.papercut.com/
// Use of this source code is governed by an MIT or GPL Version 2 license.
// See the project's LICENSE file for more information.
//
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"sync"
	"time"

	"github.com/kardianos/service"

	"github.com/robfig/cron"

	"github.com/papercutsoftware/silver/lib/logging"
	"github.com/papercutsoftware/silver/lib/pathutils"
	"github.com/papercutsoftware/silver/service/cmdutil"
	"github.com/papercutsoftware/silver/service/config"
	"github.com/papercutsoftware/silver/service/svcutil"
)

const (
	defaultRefreshPoll = 10 * time.Second
)

type context struct {
	conf         *config.Config
	terminate    chan struct{}
	logger       *log.Logger
	runningGroup sync.WaitGroup
	cronManager  *cron.Cron
}

func main() {

	ctx := &context{}

	// Parse config (we don't action any errors quite yet)
	var err error
	ctx.conf, err = loadConf()
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: Invalid config - %v\n", err)
		os.Exit(1)
	}

	action, actionArgs, err := parse(os.Args)
	if err != nil {
		printUsage(ctx.conf.ServiceDescription.DisplayName, ctx.conf.ServiceDescription.Description)
		os.Exit(1)
	}

	if err := os.Chdir(exeFolder()); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: Unable to set working directory - %v\n", err)
		os.Exit(1)
	}
	switch action {
	case "command":
		execCommand(ctx, actionArgs)
	case "validate":
		fmt.Println("Config is valid")
		os.Exit(0)
	default:
		osServiceControl(ctx)
	}
}

func osServiceControl(ctx *context) {
	// Setup log file out
	logFile := ctx.conf.ServiceConfig.LogFile
	maxSize := int64(ctx.conf.ServiceConfig.LogFileMaxSizeMb) * 1024 * 1024
	if logFile == "" {
		logFile = serviceName() + ".log"
	}
	ctx.logger = logging.NewFileLoggerWithMaxSize(logFile, maxSize)

	// Setup service
	svcConfig := &service.Config{
		Name:        serviceName(),
		DisplayName: ctx.conf.ServiceDescription.DisplayName,
		Description: ctx.conf.ServiceDescription.Description,
	}

	osService := &osService{ctx: ctx}
	svc, err := service.New(osService, svcConfig)
	if err != nil {
		fmt.Printf("ERROR: Invalid service config: %v\n", err)
		os.Exit(1)
	}

	if len(os.Args) > 1 && os.Args[1] != "run" {
		err = service.Control(svc, os.Args[1])
		if err != nil {
			fmt.Printf("ERROR: Invalid service command: %v\n", err)
			os.Exit(1)
		}
		os.Exit(0)
	}

	err = svc.Run()
	if err != nil {
		fmt.Printf("ERROR: %v\n", err)
		os.Exit(1)
	}

	pidFile := ctx.conf.ServiceConfig.PidFile
	if pidFile != "" {
		ioutil.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0644)
	}
}

func printUsage(svcDisplayName, svcDesc string) {
	fmt.Printf("%s (%s)\n", svcDisplayName,
		serviceName())
	fmt.Printf("%s\n\n", svcDesc)
	fmt.Printf("Usage:\n")
	fmt.Printf("%s [install|uninstall|start|stop|command|validate|run|help] [command-name]\n", exeName())
	fmt.Printf("  install   - Install the service.\n")
	fmt.Printf("  uninstall - Remove/uninstall the service.\n")
	fmt.Printf("  start     - Start an installed service.\n")
	fmt.Printf("  stop      - Stop an installed service.\n")
	fmt.Printf("  validate  - Test the configuration file.\n")
	fmt.Printf("  run       - Run service on in command-line mode.\n")
	fmt.Printf("  command   - Run a command [command-name].\n")
	fmt.Printf("  help      - This usage message.\n")
}

func loadConf() (conf *config.Config, err error) {
	// FIXME: Not Get this function out of utils.
	confPath := getConfigFilePath()
	vars := config.ReplacementVars{
		ServiceName: serviceName(),
		ServiceRoot: exeFolder(),
	}
	conf, err = config.LoadConfig(confPath, vars)
	if err != nil {
		return nil, err
	}

	// Merge in any include files
	for _, include := range conf.Include {
		conf, err = config.MergeInclude(*conf, include, vars)
		if err != nil {
			return nil, err
		}
	}
	return conf, err
}

func execCommand(ctx *context, args []string) {
	/*
	*  IMPORTANT:
	*  Don't write to any log files, etc.  Commands are not system service code.
	*  Commands should be thought of as a "symlink" style, and run under a different
	*  user context.
	*
	*  args format: 1st element is the command. Any extras are appended to the command.
	 */
	if len(ctx.conf.Commands) == 0 {
		fmt.Fprintf(os.Stderr, "There are no commands configured!\n")
		os.Exit(1)
	}

	var cmd *config.Command
	if len(args) > 0 {
		cmdName := args[0]
		for _, c := range ctx.conf.Commands {
			if c.Name == cmdName {
				cmd = &c
				break
			}
		}
	}

	if cmd == nil {
		// Print command usage
		fmt.Fprintf(os.Stderr, "Valid commands are:\n")
		for _, command := range ctx.conf.Commands {
			fmt.Fprintf(os.Stderr, "    %s\n", command.Name)
		}
		os.Exit(1)
	}

	cmdConf := cmdutil.CommandConfig{}
	cmdConf.Path = pathutils.FindLastFile(cmd.Path)
	// Append any extra commands
	cmdConf.Args = append(cmd.Args, args[1:]...)
	// FIXME: Maybe unit conversion should be in the config layer?
	cmdConf.ExecTimeout = (time.Second * time.Duration(cmd.TimeoutSecs))

	exitCode, err := cmdutil.Execute(cmdConf)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
	}
	os.Exit(exitCode)
}

type osService struct {
	ctx *context
}

func (o *osService) Start(s service.Service) error {
	msg := fmt.Sprintf("Service '%s' started.", serviceName())
	o.ctx.logger.Printf(msg)
	sysLogger, err := s.Logger(nil)
	if err != nil {
		sysLogger.Info(msg)
	}

	doStart(o.ctx)
	go watchForReload(o.ctx)

	return nil
}

func doStart(ctx *context) {
	sf := stopFileName(ctx)
	if sf != "" {
		os.Remove(sf)
	}
	ctx.terminate = make(chan struct{})
	execStartupTasks(ctx)
	setupScheduledTasks(ctx)
	startServices(ctx)
}

func (o *osService) Stop(s service.Service) error {
	o.ctx.logger.Printf(fmt.Sprintf("Stopping '%s' service...", serviceName()))

	doStop(o.ctx)

	pidFile := o.ctx.conf.ServiceConfig.PidFile
	if pidFile != "" {
		os.Remove(pidFile)
	}

	msg := fmt.Sprintf("Stopped '%s' service.", serviceName())
	o.ctx.logger.Printf(msg)

	sysLogger, err := s.Logger(nil)
	if err != nil {
		sysLogger.Info(msg)
	}
	return nil
}

func stopFileName(ctx *context) string {
	stopFile := ctx.conf.ServiceConfig.StopFile
	if stopFile != "disabled" {
		return ""
	}
	return stopFile
}

func doStop(ctx *context) {
	// Create stop file... another method to signal services to stop.
	sf := stopFileName(ctx)
	if sf != "" {
		ioutil.WriteFile(sf, nil, 0644)
		defer os.Remove(sf)
	}
	if ctx.cronManager != nil {
		ctx.cronManager.Stop()
		ctx.cronManager = nil
	}
	if ctx.terminate != nil {
		close(ctx.terminate)
	}
	ctx.runningGroup.Wait()
}

func watchForReload(ctx *context) {
	f := ctx.conf.ServiceConfig.ReloadFile
	for {
		// FIXME: File system notification rather than polling?
		time.Sleep(defaultRefreshPoll)
		if _, err := os.Stat(f); err == nil {
			if err := os.Remove(f); err == nil {
				ctx.logger.Printf("Reload requested. Services will now restart.")
				doStop(ctx)
				time.Sleep(time.Second)
				// Reload config
				ctx.conf, _ = loadConf()
				doStart(ctx)
			}
		}
	}
}

func execStartupTasks(ctx *context) {
	ctx.logger.Printf("Starting %d startup tasks.", len(ctx.conf.StartupTasks))
	for _, task := range ctx.conf.StartupTasks {
		runTask := func(task config.StartupTask) {
			ctx.runningGroup.Add(1)
			defer ctx.runningGroup.Done()
			taskName := path.Base(task.Path)
			taskConfig := createTaskConfig(ctx, task.Task)
			if exitCode, err := svcutil.ExecuteTask(ctx.terminate, taskConfig); err != nil {
				ctx.logger.Printf("ERROR: Startup task '%s' reported: %v", taskName, err)
			} else {
				ctx.logger.Printf("Startup task '%s' finished with exit code %d", taskName, exitCode)
			}
		}
		if task.Async {
			go runTask(task)
		} else {
			if task.StartupDelaySecs > 0 || task.StartupRandomDelaySecs > 0 {
				ctx.logger.Printf("WARNING: Only Async startup tasks should have startup delays.")
			}
			runTask(task)
		}
	}
}

func startServices(ctx *context) {
	ctx.logger.Printf("Starting %d services.", len(ctx.conf.Services))
	for _, service := range ctx.conf.Services {
		go func(service config.Service) {
			ctx.runningGroup.Add(1)
			defer ctx.runningGroup.Done()
			serviceName := path.Base(service.Path)
			svcConfig := svcutil.ServiceConfig{}
			svcConfig.Path = pathutils.FindLastFile(service.Path)
			svcConfig.Args = service.Args
			svcConfig.GracefulShutDown = time.Duration(service.GracefulShutdownTimeoutSecs) * time.Second
			svcConfig.StartupDelay = time.Duration(service.StartupDelaySecs) * time.Second
			svcConfig.Logger = ctx.logger
			svcConfig.CrashConfig = svcutil.CrashConfig{
				MaxCountPerHour: service.MaxCrashCountPerHour,
				RestartDelay:    time.Duration(service.RestartDelaySecs) * time.Second,
			}
			if service.MonitorPing != nil {
				svcConfig.MonitorConfig = svcutil.MonitorConfig{
					URL:                   service.MonitorPing.URL,
					StartupDelay:          time.Duration(service.MonitorPing.StartupDelaySecs) * time.Second,
					Interval:              time.Duration(service.MonitorPing.IntervalSecs) * time.Second,
					Timeout:               time.Duration(service.MonitorPing.TimeoutSecs) * time.Second,
					RestartOnFailureCount: service.MonitorPing.RestartOnFailureCount,
				}
			}
			if err := svcutil.ExecuteService(ctx.terminate, svcConfig); err != nil {
				ctx.logger.Printf("ERROR: Service '%s' reported: %v", serviceName, err)
			}
		}(service)
	}
}

func createTaskConfig(ctx *context, task config.Task) svcutil.TaskConfig {
	taskConfig := svcutil.TaskConfig{}
	taskConfig.Path = pathutils.FindLastFile(task.Path)
	taskConfig.Args = task.Args
	taskConfig.ExecTimeout = time.Duration(task.TimeoutSecs) * time.Second
	taskConfig.StartupDelay = time.Duration(task.StartupDelaySecs) * time.Second
	taskConfig.StartupRandomDelay = time.Duration(task.StartupRandomDelaySecs) * time.Second
	taskConfig.Logger = ctx.logger
	return taskConfig
}

func setupScheduledTasks(ctx *context) {
	ctx.logger.Printf("Setting up %d scheduled tasks.", len(ctx.conf.ScheduledTasks))
	ctx.cronManager = cron.New()
	for _, scheduledTask := range ctx.conf.ScheduledTasks {
		taskConfig := createTaskConfig(ctx, scheduledTask.Task)
		runTask := func() {
			ctx.runningGroup.Add(1)
			defer ctx.runningGroup.Done()
			taskName := path.Base(taskConfig.Path)
			ctx.logger.Printf("Running schedule task '%s'", taskName)
			if exitCode, err := svcutil.ExecuteTask(ctx.terminate, taskConfig); err != nil {
				ctx.logger.Printf("ERROR: Scheduled task '%s' reported: %v", taskName, err)
			} else {
				ctx.logger.Printf("The task '%s' finished with exit code %d", taskName, exitCode)
			}
		}
		err := ctx.cronManager.AddFunc(scheduledTask.Schedule, runTask)
		if err != nil {
			ctx.logger.Printf("Unable to schedule task '%s': %v", scheduledTask.Path, err)
		}
	}
	ctx.cronManager.Start()
}
<|endoftext|>


<commit_before>package service

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/TeaMeow/KitSvc/errno"
	"github.com/TeaMeow/KitSvc/model"
	"github.com/TeaMeow/KitSvc/module/event"
	"github.com/TeaMeow/KitSvc/module/mq"
	"github.com/TeaMeow/KitSvc/shared/auth"
	"github.com/TeaMeow/KitSvc/shared/token"
	"github.com/TeaMeow/KitSvc/shared/wsutil"
	"github.com/TeaMeow/KitSvc/store"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"github.com/olahol/melody"
)

// CreateUser creates a new user account.
func CreateUser(c *gin.Context) {
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}
	// Validate the data.
	if err := u.Validate(); err != nil {
		errno.Abort("VALIDATION_ERR", err, c)
		return
	}
	// Encrypt the user password.
	if err := u.Encrypt(); err != nil {
		errno.Abort("ENCRYPT_ERR", err, c)
		return
	}
	// Insert the user to the database.
	if err := store.CreateUser(c, &u); err != nil {
		errno.Abort("DB_ERR", err, c)
		return
	}
	// Publish the `send_mail` message to the message queue.
	go mq.SendMail(c, &u)
	// Send a `user_created` event to Event Store.
	go event.UserCreated(c, &u)

	// Show the user information.
	c.JSON(http.StatusOK, u)
}

// GetUser gets an user by the user identifier.
func GetUser(c *gin.Context) {
	// Get the `username` from the url parameter.
	username := c.Param("username")
	// Get the user by the `username` from the database.
	u, err := store.GetUser(c, username)
	if err != nil {
		errno.Abort("USER_NOT_FOUND", err, c)
		return
	}

	c.JSON(http.StatusOK, u)
}

// DeleteUser deletes the user by the user identifier.
func DeleteUser(c *gin.Context) {
	// Get the user id from the url parameter.
	userID, _ := strconv.Atoi(c.Param("id"))
	// Delete the user in the database.
	if err := store.DeleteUser(c, userID); err != nil {
		if err == gorm.ErrRecordNotFound {
			errno.Abort("USER_NOT_FOUND", err, c)
		} else {
			errno.Abort("DB_ERR", err, c)
		}
		return
	}

	c.String(http.StatusOK, "The user has been deleted successfully.")
}

// UpdateUser updates an user account information.
func UpdateUser(c *gin.Context) {
	// Get the user id from the url parameter.
	userID, _ := strconv.Atoi(c.Param("id"))

	// Binding the user data.
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}

	// We update the record based on the user id.
	u.ID = userID
	// Validate the data.
	if err := u.Validate(); err != nil {
		errno.Abort("VALIDATION_ERR", err, c)
		return
	}
	// Parse the json web token.
	if _, err := token.ParseRequest(c); err != nil {
		errno.Abort("TOKEN_INVALID", err, c)
		return
	}
	// Encrypt the user password.
	if err := u.Encrypt(); err != nil {
		errno.Abort("ENCRYPT_ERR", err, c)
		return
	}
	// Update the user in the database.
	if err := store.UpdateUser(c, &u); err != nil {
		if err == gorm.ErrRecordNotFound {
			errno.Abort("USER_NOT_FOUND", err, c)
		} else {
			errno.Abort("DB_ERR", err, c)
		}
		return
	}

	c.JSON(http.StatusOK, u)
}

// PostToken generates the authentication token
// if the password was matched with the specified account.
func PostToken(c *gin.Context) {
	// Binding the data with the user struct.
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}
	// Get the user information by the login username.
	d, err := store.GetUser(c, u.Username)
	if err != nil {
		errno.Abort("USER_NOT_FOUND", err, c)
		return
	}
	// Compare the login password with the user password.
	if err := auth.Compare(d.Password, u.Password); err != nil {
		errno.Abort("PASSWORD_INCORRECT", err, c)
		return
	}
	// Sign the json web token.
	t, err := token.Sign(c, token.Context{ID: d.ID, Username: d.Username}, "")
	if err != nil {
		errno.Abort("TOKEN_ERR", err, c)
		return
	}

	c.JSON(http.StatusOK, model.Token{Token: t})
}

// WatchUser watches the user changes, and broadcast when there's a new user.
func WatchUser(c *gin.Context) {
	ws := wsutil.Get(c)

	ws.HandleConnect(func(s *melody.Session) {
		fmt.Println("Cone")
		go func() {
			for {
				<-time.After(1 * time.Second)
				fmt.Println("ddd")
				ws.Broadcast([]byte("Wow"))
			}
		}()
	})
	fmt.Println("Here")
}

// SendMail sends the mail to the new user's inbox.
func SendMail(c *gin.Context) {
	// Blah, blah blah ...
}

// UserCreated handles the `user-created` event.
func UserCreated(c *gin.Context) {
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}
}
<commit_msg>Watching the user register event with websocket<commit_after>package service

import (
	"net/http"
	"strconv"
	"time"

	"github.com/TeaMeow/KitSvc/errno"
	"github.com/TeaMeow/KitSvc/model"
	"github.com/TeaMeow/KitSvc/module/event"
	"github.com/TeaMeow/KitSvc/module/mq"
	"github.com/TeaMeow/KitSvc/shared/auth"
	"github.com/TeaMeow/KitSvc/shared/token"
	"github.com/TeaMeow/KitSvc/shared/wsutil"
	"github.com/TeaMeow/KitSvc/store"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"github.com/olahol/melody"
)

// CreateUser creates a new user account.
func CreateUser(c *gin.Context) {
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}
	// Validate the data.
	if err := u.Validate(); err != nil {
		errno.Abort("VALIDATION_ERR", err, c)
		return
	}
	// Encrypt the user password.
	if err := u.Encrypt(); err != nil {
		errno.Abort("ENCRYPT_ERR", err, c)
		return
	}
	// Insert the user to the database.
	if err := store.CreateUser(c, &u); err != nil {
		errno.Abort("DB_ERR", err, c)
		return
	}
	// Publish the `send_mail` message to the message queue.
	go mq.SendMail(c, &u)
	// Send a `user_created` event to Event Store.
	go event.UserCreated(c, &u)

	// Show the user information.
	c.JSON(http.StatusOK, u)
}

// GetUser gets an user by the user identifier.
func GetUser(c *gin.Context) {
	// Get the `username` from the url parameter.
	username := c.Param("username")
	// Get the user by the `username` from the database.
	u, err := store.GetUser(c, username)
	if err != nil {
		errno.Abort("USER_NOT_FOUND", err, c)
		return
	}

	c.JSON(http.StatusOK, u)
}

// DeleteUser deletes the user by the user identifier.
func DeleteUser(c *gin.Context) {
	// Get the user id from the url parameter.
	userID, _ := strconv.Atoi(c.Param("id"))
	// Delete the user in the database.
	if err := store.DeleteUser(c, userID); err != nil {
		if err == gorm.ErrRecordNotFound {
			errno.Abort("USER_NOT_FOUND", err, c)
		} else {
			errno.Abort("DB_ERR", err, c)
		}
		return
	}

	c.String(http.StatusOK, "The user has been deleted successfully.")
}

// UpdateUser updates an user account information.
func UpdateUser(c *gin.Context) {
	// Get the user id from the url parameter.
	userID, _ := strconv.Atoi(c.Param("id"))

	// Binding the user data.
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}

	// We update the record based on the user id.
	u.ID = userID
	// Validate the data.
	if err := u.Validate(); err != nil {
		errno.Abort("VALIDATION_ERR", err, c)
		return
	}
	// Parse the json web token.
	if _, err := token.ParseRequest(c); err != nil {
		errno.Abort("TOKEN_INVALID", err, c)
		return
	}
	// Encrypt the user password.
	if err := u.Encrypt(); err != nil {
		errno.Abort("ENCRYPT_ERR", err, c)
		return
	}
	// Update the user in the database.
	if err := store.UpdateUser(c, &u); err != nil {
		if err == gorm.ErrRecordNotFound {
			errno.Abort("USER_NOT_FOUND", err, c)
		} else {
			errno.Abort("DB_ERR", err, c)
		}
		return
	}

	c.JSON(http.StatusOK, u)
}

// PostToken generates the authentication token
// if the password was matched with the specified account.
func PostToken(c *gin.Context) {
	// Binding the data with the user struct.
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}
	// Get the user information by the login username.
	d, err := store.GetUser(c, u.Username)
	if err != nil {
		errno.Abort("USER_NOT_FOUND", err, c)
		return
	}
	// Compare the login password with the user password.
	if err := auth.Compare(d.Password, u.Password); err != nil {
		errno.Abort("PASSWORD_INCORRECT", err, c)
		return
	}
	// Sign the json web token.
	t, err := token.Sign(c, token.Context{ID: d.ID, Username: d.Username}, "")
	if err != nil {
		errno.Abort("TOKEN_ERR", err, c)
		return
	}

	c.JSON(http.StatusOK, model.Token{Token: t})
}

// WatchUser watches the user changes, and broadcast when there's a new user.
func WatchUser(c *gin.Context) {
	// Get the WebSocket from the Gin context.
	ws := wsutil.Get(c)

	ws.HandleConnect(func(s *melody.Session) {
		go func() {
			// Get the identifier of the last user.
			l, _ := store.GetLastUser(c)

			for {
				// Slow and steady, otherwise the database will boomed.
				<-time.After(100 * time.Millisecond)
				// Get the new user who is registered after the specified user.
				u, err := store.GetUserAfter(c, l.ID)
				if err != nil {
					continue
				}
				// Boardcast the lastest username.
				ws.Broadcast([]byte(u.Username))

				l.ID++
			}
		}()
	})
}

// SendMail sends the mail to the new user's inbox.
func SendMail(c *gin.Context) {
	// Blah, blah blah ...
}

// UserCreated handles the `user-created` event.
func UserCreated(c *gin.Context) {
	var u model.User
	if err := c.Bind(&u); err != nil {
		errno.Abort("BIND_ERR", err, c)
		return
	}
}
<|endoftext|>


<commit_before>package fullerene

import (
	"time"
)

type Fullerene struct {
	t time.Time
}

func Now() Fullerene {
	return Fullerene{
		t: time.Now(),
	}
}

func (fr Fullerene) Date() (year int, month time.Month, day int) {
	return fr.t.Date()
}

func (fr Fullerene) After(u Fullerene) bool {
	return fr.t.After(u.t)
}

func (fr Fullerene) Before(u Fullerene) bool {
	return fr.t.Before(u.t)
}

func (fr Fullerene) Equal(u Fullerene) bool {
	return fr.t.Equal(u.t)
}

func (fr Fullerene) IsZero() bool {
	return fr.t.IsZero()
}

func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Fullerene {
	return Fullerene{t: time.Date(year, month, day, hour, min, sec, nsec, loc)}
}

func (fr Fullerene) Year() int {
	return fr.t.Year()
}

func (fr Fullerene) Month() time.Month {
	return fr.t.Month()
}

func (fr Fullerene) Day() int {
	return fr.t.Day()
}

func (fr Fullerene) AddDate(years int, months int, days int) Fullerene {
	return Fullerene{t: fr.t.AddDate(years, months, days)}
}

func (fr Fullerene) IsLeapYear() bool {
	y := fr.Year()
	if y%4 != 0 {
		return false
	}
	if y%100 == 0 && y%400 != 0 {
		return false
	}
	return true
}

func (fr Fullerene) IsLeapDay() bool {
	_, m, d := fr.Date()
	if m == 2 && d == 29 {
		return true
	}
	return false
}

func (fr Fullerene) IsBirthday(targetTime Fullerene, beforeDayIfLeap bool) bool {
	_, m, d := fr.Date()           // birthday
	_, mm, dd := targetTime.Date() // check if it is birthday.
	if m == mm && d == dd && !fr.IsLeapDay() {
		// consider leap year.
		return true
	}

	// there are countries which a person get old at the day before leap day, and the day after in a leap year.
	return fr.isBirthdayEx(targetTime, beforeDayIfLeap)
}

func (fr Fullerene) isBirthdayEx(targetTime Fullerene, beforeDayIfLeap bool) bool {
	_, m, d := targetTime.Date()
	if targetTime.IsLeapYear() {
		return false
	}
	if beforeDayIfLeap && m == 2 && d == 28 {
		return true
	}
	if !beforeDayIfLeap && m == 3 && d == 1 {
		return true
	}
	return false
}
<commit_msg>Refactoring leap year method.<commit_after>package fullerene

import (
	"time"
)

type Fullerene struct {
	t time.Time
}

func Now() Fullerene {
	return Fullerene{
		t: time.Now(),
	}
}

func (fr Fullerene) Date() (year int, month time.Month, day int) {
	return fr.t.Date()
}

func (fr Fullerene) After(u Fullerene) bool {
	return fr.t.After(u.t)
}

func (fr Fullerene) Before(u Fullerene) bool {
	return fr.t.Before(u.t)
}

func (fr Fullerene) Equal(u Fullerene) bool {
	return fr.t.Equal(u.t)
}

func (fr Fullerene) IsZero() bool {
	return fr.t.IsZero()
}

func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Fullerene {
	return Fullerene{t: time.Date(year, month, day, hour, min, sec, nsec, loc)}
}

func (fr Fullerene) Year() int {
	return fr.t.Year()
}

func (fr Fullerene) Month() time.Month {
	return fr.t.Month()
}

func (fr Fullerene) Day() int {
	return fr.t.Day()
}

func (fr Fullerene) AddDate(years int, months int, days int) Fullerene {
	return Fullerene{t: fr.t.AddDate(years, months, days)}
}

func (fr Fullerene) IsLeapYear() bool {
	y := fr.Year()
	return (y%4 == 0 && (y%100 != 0 || y%400 == 0))
}

func (fr Fullerene) IsLeapDay() bool {
	_, m, d := fr.Date()
	return (m == 2 && d == 29)
}

func (fr Fullerene) IsBirthday(targetTime Fullerene, beforeDayIfLeap bool) bool {
	_, m, d := fr.Date()           // birthday
	_, mm, dd := targetTime.Date() // check if it is birthday.
	if m == mm && d == dd && !fr.IsLeapDay() {
		// consider leap year.
		return true
	}

	// there are countries which a person get old at the day before leap day, and the day after in a leap year.
	return fr.isBirthdayEx(targetTime, beforeDayIfLeap)
}

func (fr Fullerene) isBirthdayEx(targetTime Fullerene, beforeDayIfLeap bool) bool {
	_, m, d := targetTime.Date()
	if targetTime.IsLeapYear() {
		return false
	}
	if beforeDayIfLeap && m == 2 && d == 28 {
		return true
	}
	if !beforeDayIfLeap && m == 3 && d == 1 {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2014 The Gman Authors. All rights reserved.
// Use of this source code is governed by an BSD-style
// license that can be found in LICENSE file.

/*
 * gman is a man-like help facility with the following features:
 *     - ability to show just certain pieces/sections of a gman page.
 *     - TLDR sections for the user on the go.
 *     - ability to leverage existing man pages, if needed
 *     - ability to start http server for interactive browsing
 *     - other stuff.
 *
 * See https://github.com/grymoire7/gman for details.
 *
 * Developed with Go 1.1.2
 */

package main

import (
    "github.com/grymoire7/blackfriday"   // markdown parser
    "bufio"         // for section extraction
    "bytes"         // for section extraction
    "compress/gzip" // for gzip io
    "errors"        // for reporting errors
    "fmt"           // for printing runtime errors
    "io"            // for piping through pager
    "io/ioutil"     // for reading files and logging
    "log"           // for debug logging
    "os"            // for local file access
    "os/exec"       // for piping through pager
    "strings"       // for string manipulation
)

func init() {
}

func main() {
    // Get configuration options from rc files and command line.
    opts := readConfig()

    terminalFlags := 0

    // Discard logging messages if not in debug mode.
    if debug, ok := opts["--debug"]; ok && debug.(bool) {
        log.Println("Debug on")
        terminalFlags |= blackfriday.TERM_DEBUG_LOGGING
    } else {
        log.SetOutput(ioutil.Discard)
    }

    // log configuration options
    for k, v := range opts {
        if !strings.HasPrefix(k, "_") {
            log.Printf("opt: %s => %v\n", k, v)
        }
    }

    // TODO: Should be more robust. Should search a path collection and allow
    // missing os and lang dirs.
    page := opts["<page>"].(string)
    gmanpath := opts["gmanpath"].(string)
    pagepath := gmanpath + "/linux/en/gman1/" + page + ".1.md"
    gzpagepath := gmanpath + "/linux/en/gman1/" + page + ".1.gz"

    var input []byte
    var err error

    // try to open the compressed version first
    f, err := os.Open(gzpagepath)
    if err != nil {
        // else try to open the non-compressed version
        if input, err = ioutil.ReadFile(pagepath); err != nil {
            fmt.Fprintln(os.Stderr, "gman: help page not found")
            log.Println("Error reading from", pagepath, ":", err)
            os.Exit(-1)
        }
    } else {
        defer f.Close()
        gz, err := gzip.NewReader(f)
        if err != nil {
            fmt.Fprintln(os.Stderr, "gman: help page not found")
            log.Println("Error reading from", gzpagepath, ":", err)
            os.Exit(-1)
        } else {
            // is there a better way to do this?
            buf := bytes.NewBuffer(input)
            _, err := io.Copy(buf, gz)
            if err != nil {
                fmt.Fprintln(os.Stderr, "gman: help page not found")
                log.Println("Error reading from", gzpagepath, ":", err)
                os.Exit(-1)
            }
            input = buf.Bytes()
        }
    }

    // handle section extraction option
    if opts["-s"] != nil {
        log.Println("Exracting", opts["-s"], "...")
        c, err := extractDocSection(input, opts["-s"].(string))
        if err == nil {
            input = c
        } else {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(-1)
        }
    }

    extensions := 0
    extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
    extensions |= blackfriday.EXTENSION_TABLES
    extensions |= blackfriday.EXTENSION_FENCED_CODE
    extensions |= blackfriday.EXTENSION_AUTOLINK

    renderer := blackfriday.TerminalRenderer(terminalFlags)
    output := blackfriday.Markdown(input, renderer, extensions)

    pager := opts["--pager"].(string)
    if pager == "nil" || pager == "null" {
        fmt.Println(string(output))
        os.Exit(0)
    }

    // declare the pager in raw mode
    cmd := exec.Command("less", "-R")
    if opts["--pager"] != "less" {
        cmd = exec.Command(pager)
    }

    // create a blocking pipe
    r, stdin := io.Pipe()
    // set the ins and outs
    cmd.Stdin = r
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    // Create a blocking chan, run the pager and unblock once it is finished
    c := make(chan struct{})
    go func() {
        defer close(c)
        cmd.Run()
    }()

    // Pass output to the pipe
    fmt.Fprintf(stdin, string(output))

    // Close stdin (allows pager to exit)
    stdin.Close()

    // Wait for the pager to be finished
    <-c
}

func extractDocSection(input []byte, sectionPattern string) ([]byte, error) {
    var lines []string
    var inSection = false
    var foundSection = false
    var sectionLevel = 0
    scanner := bufio.NewScanner(bytes.NewReader(input))
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "#") {
            var level = 0
            for _, c := range line {
                if c != '#' {
                    break
                }
                level++
            }
            if inSection {
                if level <= sectionLevel {
                    inSection = false
                }
            } else {
                if strings.Contains(line, sectionPattern) {
                    inSection = true
                    foundSection = true
                    sectionLevel = level
                }
            }
        }
        if inSection {
            lines = append(lines, scanner.Text())
        }
    }
    s := strings.Join(lines, "\n")
    if !foundSection {
        return nil, errors.New("gman: document section not found")
    }
    return []byte(s), scanner.Err()
}
<commit_msg>add page name to error message<commit_after>// Copyright 2014 The Gman Authors. All rights reserved.
// Use of this source code is governed by an BSD-style
// license that can be found in LICENSE file.

/*
 * gman is a man-like help facility with the following features:
 *     - ability to show just certain pieces/sections of a gman page.
 *     - TLDR sections for the user on the go.
 *     - ability to leverage existing man pages, if needed
 *     - ability to start http server for interactive browsing
 *     - other stuff.
 *
 * See https://github.com/grymoire7/gman for details.
 *
 * Developed with Go 1.1.2
 */

package main

import (
    "github.com/grymoire7/blackfriday"   // markdown parser
    "bufio"         // for section extraction
    "bytes"         // for section extraction
    "compress/gzip" // for gzip io
    "errors"        // for reporting errors
    "fmt"           // for printing runtime errors
    "io"            // for piping through pager
    "io/ioutil"     // for reading files and logging
    "log"           // for debug logging
    "os"            // for local file access
    "os/exec"       // for piping through pager
    "strings"       // for string manipulation
)

func init() {
}

func main() {
    // Get configuration options from rc files and command line.
    opts := readConfig()

    terminalFlags := 0

    // Discard logging messages if not in debug mode.
    if debug, ok := opts["--debug"]; ok && debug.(bool) {
        log.Println("Debug on")
        terminalFlags |= blackfriday.TERM_DEBUG_LOGGING
    } else {
        log.SetOutput(ioutil.Discard)
    }

    // log configuration options
    for k, v := range opts {
        if !strings.HasPrefix(k, "_") {
            log.Printf("opt: %s => %v\n", k, v)
        }
    }

    // TODO: Should be more robust. Should search a path collection and allow
    // missing os and lang dirs.
    page := opts["<page>"].(string)
    gmanpath := opts["gmanpath"].(string)
    pagepath := gmanpath + "/linux/en/gman1/" + page + ".1.md"
    gzpagepath := gmanpath + "/linux/en/gman1/" + page + ".1.gz"

    var input []byte
    var err error

    // try to open the compressed version first
    f, err := os.Open(gzpagepath)
    if err != nil {
        // else try to open the non-compressed version
        if input, err = ioutil.ReadFile(pagepath); err != nil {
            fmt.Fprintln(os.Stderr, "gman: help page", page, "not found")
            log.Println("Error reading from", pagepath, ":", err)
            os.Exit(-1)
        }
    } else {
        defer f.Close()
        gz, err := gzip.NewReader(f)
        if err != nil {
            fmt.Fprintln(os.Stderr, "gman: help page", page, "not found")
            log.Println("Error reading from", gzpagepath, ":", err)
            os.Exit(-1)
        } else {
            // is there a better way to do this?
            buf := bytes.NewBuffer(input)
            _, err := io.Copy(buf, gz)
            if err != nil {
                fmt.Fprintln(os.Stderr, "gman: help page", page, "not found")
                log.Println("Error reading from", gzpagepath, ":", err)
                os.Exit(-1)
            }
            input = buf.Bytes()
        }
    }

    // handle section extraction option
    if opts["-s"] != nil {
        log.Println("Exracting", opts["-s"], "...")
        c, err := extractDocSection(input, opts["-s"].(string))
        if err == nil {
            input = c
        } else {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(-1)
        }
    }

    extensions := 0
    extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
    extensions |= blackfriday.EXTENSION_TABLES
    extensions |= blackfriday.EXTENSION_FENCED_CODE
    extensions |= blackfriday.EXTENSION_AUTOLINK

    renderer := blackfriday.TerminalRenderer(terminalFlags)
    output := blackfriday.Markdown(input, renderer, extensions)

    pager := opts["--pager"].(string)
    if pager == "nil" || pager == "null" {
        fmt.Println(string(output))
        os.Exit(0)
    }

    // declare the pager in raw mode
    cmd := exec.Command("less", "-R")
    if opts["--pager"] != "less" {
        cmd = exec.Command(pager)
    }

    // create a blocking pipe
    r, stdin := io.Pipe()
    // set the ins and outs
    cmd.Stdin = r
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    // Create a blocking chan, run the pager and unblock once it is finished
    c := make(chan struct{})
    go func() {
        defer close(c)
        cmd.Run()
    }()

    // Pass output to the pipe
    fmt.Fprintf(stdin, string(output))

    // Close stdin (allows pager to exit)
    stdin.Close()

    // Wait for the pager to be finished
    <-c
}

func extractDocSection(input []byte, sectionPattern string) ([]byte, error) {
    var lines []string
    var inSection = false
    var foundSection = false
    var sectionLevel = 0
    scanner := bufio.NewScanner(bytes.NewReader(input))
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "#") {
            var level = 0
            for _, c := range line {
                if c != '#' {
                    break
                }
                level++
            }
            if inSection {
                if level <= sectionLevel {
                    inSection = false
                }
            } else {
                if strings.Contains(line, sectionPattern) {
                    inSection = true
                    foundSection = true
                    sectionLevel = level
                }
            }
        }
        if inSection {
            lines = append(lines, scanner.Text())
        }
    }
    s := strings.Join(lines, "\n")
    if !foundSection {
        return nil, errors.New("gman: document section not found")
    }
    return []byte(s), scanner.Err()
}
<|endoftext|>


<commit_before>// Copyright 2015-present, Cyrill @ Schumacher.fm and the CoreStore contributors
//
// 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 null_test

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/corestoreio/errors"
	"github.com/corestoreio/pkg/sql/dml"
	"github.com/corestoreio/pkg/sql/dmltest"
	"github.com/corestoreio/pkg/storage/null"
	"github.com/corestoreio/pkg/util/assert"
)

const tableNullTypesCreate = `CREATE TABLE dml_null_types (
  id int(11) NOT NULL AUTO_INCREMENT,
  string_val varchar(255) DEFAULT NULL,
  int64_val int(11) DEFAULT NULL,
  float64_val float DEFAULT NULL,
  time_val datetime DEFAULT NULL,
  bool_val tinyint(1) DEFAULT NULL,
  decimal_val decimal(5,3) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`

const tableNullTypesDrop = `DROP TABLE IF EXISTS dml_null_types`

func TestDecimal_Select_Integration(t *testing.T) {
	ctx := context.TODO()
	dbc := dmltest.MustConnectDB(t, dml.WithSetNamesUTF8MB4(),
		dml.WithExecSQLOnConnOpen(ctx, tableNullTypesCreate), dml.WithExecSQLOnConnClose(ctx, tableNullTypesDrop))
	defer dmltest.Close(t, dbc)

	rec := newNullTypedRecordWithData()
	in := dbc.InsertInto("dml_null_types").
		AddColumns("id", "string_val", "int64_val", "float64_val", "time_val", "bool_val", "decimal_val")

	res, err := in.WithArgs().Record("", rec).ExecContext(context.TODO())
	assert.NoError(t, err)
	id, err := res.LastInsertId()
	assert.NoError(t, err)
	assert.Exactly(t, int64(2), id)

	nullTypeSet := &nullTypedRecord{}
	dec := null.Decimal{Precision: 12345, Scale: 3, Valid: true}

	sel := dbc.SelectFrom("dml_null_types").Star().Where(
		dml.Column("decimal_val").Decimal(dec),
	)

	rc, err := sel.WithArgs().Load(context.TODO(), nullTypeSet)
	assert.NoError(t, err)
	assert.Exactly(t, uint64(1), rc)

	assert.Exactly(t, rec, nullTypeSet)
}

func TestNullTypeScanning(t *testing.T) {
	ctx := context.TODO()
	dbc := dmltest.MustConnectDB(t, dml.WithSetNamesUTF8MB4(),
		dml.WithExecSQLOnConnOpen(ctx, tableNullTypesCreate), dml.WithExecSQLOnConnClose(ctx, tableNullTypesDrop))
	defer dmltest.Close(t, dbc)

	type nullTypeScanningTest struct {
		record *nullTypedRecord
		valid  bool
	}

	tests := []nullTypeScanningTest{
		{
			record: &nullTypedRecord{ID: 1},
			valid:  false,
		},
		{
			record: newNullTypedRecordWithData(),
			valid:  true,
		},
	}

	for _, test := range tests {
		// Create the record in the db
		res, err := dbc.InsertInto("dml_null_types").
			AddColumns("string_val", "int64_val", "float64_val", "time_val", "bool_val", "decimal_val").
			WithArgs().Record("", test.record).ExecContext(context.TODO())
		assert.NoError(t, err)
		id, err := res.LastInsertId()
		assert.NoError(t, err)

		// Scan it back and check that all fields are of the correct validity and are
		// equal to the reference record
		nullTypeSet := &nullTypedRecord{}
		_, err = dbc.SelectFrom("dml_null_types").Star().Where(
			dml.Expr("id = ?").Int64(id),
		).WithArgs().Load(context.TODO(), nullTypeSet)
		assert.NoError(t, err)

		assert.Equal(t, test.record, nullTypeSet)
		assert.Equal(t, test.valid, nullTypeSet.StringVal.Valid)
		assert.Equal(t, test.valid, nullTypeSet.Int64Val.Valid)
		assert.Equal(t, test.valid, nullTypeSet.Float64Val.Valid)
		assert.Equal(t, test.valid, nullTypeSet.TimeVal.Valid)
		assert.Equal(t, test.valid, nullTypeSet.BoolVal.Valid)
		assert.Equal(t, test.valid, nullTypeSet.DecimalVal.Valid)

		nullTypeSet.StringVal.String = "newStringVal"
		assert.NotEqual(t, test.record, nullTypeSet)
	}
}

func TestNullTypeJSONMarshal(t *testing.T) {

	type nullTypeJSONTest struct {
		record       *nullTypedRecord
		expectedJSON []byte
	}

	tests := []nullTypeJSONTest{
		{
			record:       &nullTypedRecord{},
			expectedJSON: []byte("{\"ID\":0,\"StringVal\":null,\"Int64Val\":null,\"Float64Val\":null,\"TimeVal\":null,\"BoolVal\":null,\"DecimalVal\":null}"),
		},
		{
			record:       newNullTypedRecordWithData(),
			expectedJSON: []byte("{\"ID\":2,\"StringVal\":\"wow\",\"Int64Val\":42,\"Float64Val\":1.618,\"TimeVal\":\"2009-01-03T18:15:05Z\",\"BoolVal\":true,\"DecimalVal\":12.345}"),
		},
	}

	for _, test := range tests {
		// Marshal the record
		rawJSON, err := json.Marshal(test.record)
		assert.NoError(t, err)
		assert.Equal(t, string(test.expectedJSON), string(rawJSON))

		// Unmarshal it back
		newRecord := &nullTypedRecord{}
		err = json.Unmarshal([]byte(rawJSON), newRecord)
		assert.NoError(t, err)
		assert.Equal(t, test.record, newRecord)
	}
}

var _ dml.ColumnMapper = (*nullTypedRecord)(nil)

type nullTypedRecord struct {
	ID         int64
	StringVal  null.String
	Int64Val   null.Int64
	Float64Val null.Float64
	TimeVal    null.Time
	BoolVal    null.Bool
	DecimalVal null.Decimal
}

func (p *nullTypedRecord) MapColumns(cm *dml.ColumnMap) error {
	if cm.Mode() == dml.ColumnMapEntityReadAll {
		return cm.Int64(&p.ID).NullString(&p.StringVal).NullInt64(&p.Int64Val).NullFloat64(&p.Float64Val).NullTime(&p.TimeVal).NullBool(&p.BoolVal).Decimal(&p.DecimalVal).Err()
	}
	for cm.Next() {
		c := cm.Column()
		switch c {
		case "id":
			cm.Int64(&p.ID)
		case "string_val":
			cm.NullString(&p.StringVal)
		case "int64_val":
			cm.NullInt64(&p.Int64Val)
		case "float64_val":
			cm.NullFloat64(&p.Float64Val)
		case "time_val":
			cm.NullTime(&p.TimeVal)
		case "bool_val":
			cm.NullBool(&p.BoolVal)
		case "decimal_val":
			cm.Decimal(&p.DecimalVal)
		default:
			return errors.NotFound.Newf("[dml_test] Column %q not found", c)
		}
	}
	return cm.Err()
}
func newNullTypedRecordWithData() *nullTypedRecord {
	return &nullTypedRecord{
		ID:         2,
		StringVal:  null.String{String: "wow", Valid: true},
		Int64Val:   null.Int64{Int64: 42, Valid: true},
		Float64Val: null.Float64{Float64: 1.618, Valid: true},
		TimeVal:    null.Time{Time: time.Date(2009, 1, 3, 18, 15, 5, 0, time.UTC), Valid: true},
		BoolVal:    null.Bool{Bool: true, Valid: true},
		DecimalVal: null.Decimal{Precision: 12345, Scale: 3, Valid: true},
	}
}
<commit_msg>storage/null: TestDecimal_Select_Integration Drop table at the beginning<commit_after>// Copyright 2015-present, Cyrill @ Schumacher.fm and the CoreStore contributors
//
// 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 null_test

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/corestoreio/errors"
	"github.com/corestoreio/pkg/sql/dml"
	"github.com/corestoreio/pkg/sql/dmltest"
	"github.com/corestoreio/pkg/storage/null"
	"github.com/corestoreio/pkg/util/assert"
)

const tableNullTypesCreate = `CREATE TABLE storage_null_types (
  id int(11) NOT NULL AUTO_INCREMENT,
  string_val varchar(255) DEFAULT NULL,
  int64_val int(11) DEFAULT NULL,
  float64_val float DEFAULT NULL,
  time_val datetime DEFAULT NULL,
  bool_val tinyint(1) DEFAULT NULL,
  decimal_val decimal(5,3) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`

const tableNullTypesDrop = `DROP TABLE IF EXISTS storage_null_types`

func TestDecimal_Select_Integration(t *testing.T) {
	ctx := context.TODO()
	dbc := dmltest.MustConnectDB(t, dml.WithSetNamesUTF8MB4(),
		dml.WithExecSQLOnConnOpen(ctx, tableNullTypesDrop),
		dml.WithExecSQLOnConnOpen(ctx, tableNullTypesCreate),
		dml.WithExecSQLOnConnClose(ctx, tableNullTypesDrop),
	)
	defer dmltest.Close(t, dbc)

	rec := newNullTypedRecordWithData()
	in := dbc.InsertInto("storage_null_types").
		AddColumns("id", "string_val", "int64_val", "float64_val", "time_val", "bool_val", "decimal_val")

	res, err := in.WithArgs().Record("", rec).ExecContext(context.TODO())
	assert.NoError(t, err)
	id, err := res.LastInsertId()
	assert.NoError(t, err)
	assert.Exactly(t, int64(2), id)

	nullTypeSet := &nullTypedRecord{}
	dec := null.Decimal{Precision: 12345, Scale: 3, Valid: true}

	sel := dbc.SelectFrom("storage_null_types").Star().Where(
		dml.Column("decimal_val").Decimal(dec),
	)

	rc, err := sel.WithArgs().Load(context.TODO(), nullTypeSet)
	assert.NoError(t, err)
	assert.Exactly(t, uint64(1), rc)

	assert.Exactly(t, rec, nullTypeSet)
}

func TestNullTypeScanning(t *testing.T) {
	ctx := context.TODO()
	dbc := dmltest.MustConnectDB(t, dml.WithSetNamesUTF8MB4(),
		dml.WithExecSQLOnConnOpen(ctx, tableNullTypesCreate), dml.WithExecSQLOnConnClose(ctx, tableNullTypesDrop))
	defer dmltest.Close(t, dbc)

	type nullTypeScanningTest struct {
		record *nullTypedRecord
		valid  bool
	}

	tests := []nullTypeScanningTest{
		{
			record: &nullTypedRecord{ID: 1},
			valid:  false,
		},
		{
			record: newNullTypedRecordWithData(),
			valid:  true,
		},
	}

	for _, test := range tests {
		// Create the record in the db
		res, err := dbc.InsertInto("storage_null_types").
			AddColumns("string_val", "int64_val", "float64_val", "time_val", "bool_val", "decimal_val").
			WithArgs().Record("", test.record).ExecContext(context.TODO())
		assert.NoError(t, err)
		id, err := res.LastInsertId()
		assert.NoError(t, err)

		// Scan it back and check that all fields are of the correct validity and are
		// equal to the reference record
		nullTypeSet := &nullTypedRecord{}
		_, err = dbc.SelectFrom("storage_null_types").Star().Where(
			dml.Expr("id = ?").Int64(id),
		).WithArgs().Load(context.TODO(), nullTypeSet)
		assert.NoError(t, err)

		assert.Equal(t, test.record, nullTypeSet)
		assert.Equal(t, test.valid, nullTypeSet.StringVal.Valid)
		assert.Equal(t, test.valid, nullTypeSet.Int64Val.Valid)
		assert.Equal(t, test.valid, nullTypeSet.Float64Val.Valid)
		assert.Equal(t, test.valid, nullTypeSet.TimeVal.Valid)
		assert.Equal(t, test.valid, nullTypeSet.BoolVal.Valid)
		assert.Equal(t, test.valid, nullTypeSet.DecimalVal.Valid)

		nullTypeSet.StringVal.String = "newStringVal"
		assert.NotEqual(t, test.record, nullTypeSet)
	}
}

func TestNullTypeJSONMarshal(t *testing.T) {

	type nullTypeJSONTest struct {
		record       *nullTypedRecord
		expectedJSON []byte
	}

	tests := []nullTypeJSONTest{
		{
			record:       &nullTypedRecord{},
			expectedJSON: []byte("{\"ID\":0,\"StringVal\":null,\"Int64Val\":null,\"Float64Val\":null,\"TimeVal\":null,\"BoolVal\":null,\"DecimalVal\":null}"),
		},
		{
			record:       newNullTypedRecordWithData(),
			expectedJSON: []byte("{\"ID\":2,\"StringVal\":\"wow\",\"Int64Val\":42,\"Float64Val\":1.618,\"TimeVal\":\"2009-01-03T18:15:05Z\",\"BoolVal\":true,\"DecimalVal\":12.345}"),
		},
	}

	for _, test := range tests {
		// Marshal the record
		rawJSON, err := json.Marshal(test.record)
		assert.NoError(t, err)
		assert.Equal(t, string(test.expectedJSON), string(rawJSON))

		// Unmarshal it back
		newRecord := &nullTypedRecord{}
		err = json.Unmarshal([]byte(rawJSON), newRecord)
		assert.NoError(t, err)
		assert.Equal(t, test.record, newRecord)
	}
}

var _ dml.ColumnMapper = (*nullTypedRecord)(nil)

type nullTypedRecord struct {
	ID         int64
	StringVal  null.String
	Int64Val   null.Int64
	Float64Val null.Float64
	TimeVal    null.Time
	BoolVal    null.Bool
	DecimalVal null.Decimal
}

func (p *nullTypedRecord) MapColumns(cm *dml.ColumnMap) error {
	if cm.Mode() == dml.ColumnMapEntityReadAll {
		return cm.Int64(&p.ID).NullString(&p.StringVal).NullInt64(&p.Int64Val).NullFloat64(&p.Float64Val).NullTime(&p.TimeVal).NullBool(&p.BoolVal).Decimal(&p.DecimalVal).Err()
	}
	for cm.Next() {
		c := cm.Column()
		switch c {
		case "id":
			cm.Int64(&p.ID)
		case "string_val":
			cm.NullString(&p.StringVal)
		case "int64_val":
			cm.NullInt64(&p.Int64Val)
		case "float64_val":
			cm.NullFloat64(&p.Float64Val)
		case "time_val":
			cm.NullTime(&p.TimeVal)
		case "bool_val":
			cm.NullBool(&p.BoolVal)
		case "decimal_val":
			cm.Decimal(&p.DecimalVal)
		default:
			return errors.NotFound.Newf("[dml_test] Column %q not found", c)
		}
	}
	return cm.Err()
}
func newNullTypedRecordWithData() *nullTypedRecord {
	return &nullTypedRecord{
		ID:         2,
		StringVal:  null.String{String: "wow", Valid: true},
		Int64Val:   null.Int64{Int64: 42, Valid: true},
		Float64Val: null.Float64{Float64: 1.618, Valid: true},
		TimeVal:    null.Time{Time: time.Date(2009, 1, 3, 18, 15, 5, 0, time.UTC), Valid: true},
		BoolVal:    null.Bool{Bool: true, Valid: true},
		DecimalVal: null.Decimal{Precision: 12345, Scale: 3, Valid: true},
	}
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2020 Red Hat, Inc.
 *
 */

package libvmi

import (
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"
	"k8s.io/utils/pointer"

	v1 "kubevirt.io/api/core/v1"
)

// Option represents an action that enables an option.
type Option func(vmi *v1.VirtualMachineInstance)

// New instantiates a new VMI configuration,
// building its properties based on the specified With* options.
func New(opts ...Option) *v1.VirtualMachineInstance {
	vmi := baseVmi(randName())

	WithTerminationGracePeriod(0)(vmi)
	for _, f := range opts {
		f(vmi)
	}

	return vmi
}

// randName returns a random name for a virtual machine
func randName() string {
	const randomPostfixLen = 5
	return "testvmi" + "-" + rand.String(randomPostfixLen)
}

// WithLabel sets a label with specified value
func WithLabel(key, value string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Labels == nil {
			vmi.Labels = map[string]string{}
		}
		vmi.Labels[key] = value
	}
}

// WithAnnotation adds an annotation with specified value
func WithAnnotation(key, value string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Annotations == nil {
			vmi.Annotations = map[string]string{}
		}
		vmi.Annotations[key] = value
	}
}

// WithTerminationGracePeriod specifies the termination grace period in seconds.
func WithTerminationGracePeriod(seconds int64) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.TerminationGracePeriodSeconds = &seconds
	}
}

// WithRng adds `rng` to the the vmi devices.
func WithRng() Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Domain.Devices.Rng = &v1.Rng{}
	}
}

// WithResourceMemory specifies the vmi memory resource.
func WithResourceMemory(value string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Domain.Resources.Requests = k8sv1.ResourceList{
			k8sv1.ResourceMemory: resource.MustParse(value),
		}
	}
}

// WithNodeSelectorFor ensures that the VMI gets scheduled on the specified node
func WithNodeSelectorFor(node *k8sv1.Node) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Spec.NodeSelector == nil {
			vmi.Spec.NodeSelector = map[string]string{}
		}
		vmi.Spec.NodeSelector["kubernetes.io/hostname"] = node.Name
	}
}

// WithUefi configures EFI bootloader and SecureBoot.
func WithUefi(secureBoot bool) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Domain.Firmware = &v1.Firmware{
			Bootloader: &v1.Bootloader{
				EFI: &v1.EFI{
					SecureBoot: pointer.BoolPtr(secureBoot),
				},
			},
		}
	}
}

// WithSEV adds `launchSecurity` with `sev`.
func WithSEV() Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Domain.LaunchSecurity = &v1.LaunchSecurity{
			SEV: &v1.SEV{},
		}
	}
}

func baseVmi(name string) *v1.VirtualMachineInstance {
	vmi := v1.NewVMIReferenceFromNameWithNS("", name)
	vmi.Spec = v1.VirtualMachineInstanceSpec{Domain: v1.DomainSpec{}}
	vmi.TypeMeta = k8smetav1.TypeMeta{
		APIVersion: v1.GroupVersion.String(),
		Kind:       "VirtualMachineInstance",
	}
	return vmi
}
<commit_msg>tests, libvmi: Do not override dependent spec sub trees Change inner field instead.<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2020 Red Hat, Inc.
 *
 */

package libvmi

import (
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"
	"k8s.io/utils/pointer"

	v1 "kubevirt.io/api/core/v1"
)

// Option represents an action that enables an option.
type Option func(vmi *v1.VirtualMachineInstance)

// New instantiates a new VMI configuration,
// building its properties based on the specified With* options.
func New(opts ...Option) *v1.VirtualMachineInstance {
	vmi := baseVmi(randName())

	WithTerminationGracePeriod(0)(vmi)
	for _, f := range opts {
		f(vmi)
	}

	return vmi
}

// randName returns a random name for a virtual machine
func randName() string {
	const randomPostfixLen = 5
	return "testvmi" + "-" + rand.String(randomPostfixLen)
}

// WithLabel sets a label with specified value
func WithLabel(key, value string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Labels == nil {
			vmi.Labels = map[string]string{}
		}
		vmi.Labels[key] = value
	}
}

// WithAnnotation adds an annotation with specified value
func WithAnnotation(key, value string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Annotations == nil {
			vmi.Annotations = map[string]string{}
		}
		vmi.Annotations[key] = value
	}
}

// WithTerminationGracePeriod specifies the termination grace period in seconds.
func WithTerminationGracePeriod(seconds int64) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.TerminationGracePeriodSeconds = &seconds
	}
}

// WithRng adds `rng` to the the vmi devices.
func WithRng() Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Domain.Devices.Rng = &v1.Rng{}
	}
}

// WithResourceMemory specifies the vmi memory resource.
func WithResourceMemory(value string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Spec.Domain.Resources.Requests == nil {
			vmi.Spec.Domain.Resources.Requests = k8sv1.ResourceList{}
		}
		vmi.Spec.Domain.Resources.Requests[k8sv1.ResourceMemory] = resource.MustParse(value)
	}
}

// WithNodeSelectorFor ensures that the VMI gets scheduled on the specified node
func WithNodeSelectorFor(node *k8sv1.Node) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Spec.NodeSelector == nil {
			vmi.Spec.NodeSelector = map[string]string{}
		}
		vmi.Spec.NodeSelector["kubernetes.io/hostname"] = node.Name
	}
}

// WithUefi configures EFI bootloader and SecureBoot.
func WithUefi(secureBoot bool) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Spec.Domain.Firmware == nil {
			vmi.Spec.Domain.Firmware = &v1.Firmware{}
		}
		if vmi.Spec.Domain.Firmware.Bootloader == nil {
			vmi.Spec.Domain.Firmware.Bootloader = &v1.Bootloader{}
		}
		if vmi.Spec.Domain.Firmware.Bootloader.EFI == nil {
			vmi.Spec.Domain.Firmware.Bootloader.EFI = &v1.EFI{}
		}
		vmi.Spec.Domain.Firmware.Bootloader.EFI.SecureBoot = pointer.Bool(secureBoot)
	}
}

// WithSEV adds `launchSecurity` with `sev`.
func WithSEV() Option {
	return func(vmi *v1.VirtualMachineInstance) {
		if vmi.Spec.Domain.LaunchSecurity == nil {
			vmi.Spec.Domain.LaunchSecurity = &v1.LaunchSecurity{}
		}
		vmi.Spec.Domain.LaunchSecurity.SEV = &v1.SEV{}
	}
}

func baseVmi(name string) *v1.VirtualMachineInstance {
	vmi := v1.NewVMIReferenceFromNameWithNS("", name)
	vmi.Spec = v1.VirtualMachineInstanceSpec{Domain: v1.DomainSpec{}}
	vmi.TypeMeta = k8smetav1.TypeMeta{
		APIVersion: v1.GroupVersion.String(),
		Kind:       "VirtualMachineInstance",
	}
	return vmi
}
<|endoftext|>