<commit_before>package proxy

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/service/sts/stsiface"
	"github.com/pkg/errors"
)

var credsRegex = regexp.MustCompile("^/(.+?)/meta-data/iam/security-credentials/(.*)$")

type metadataCredentials struct {
	Code            string
	LastUpdated     time.Time
	Type            string
	AccessKeyID     string `json:"AccessKeyId"`
	SecretAccessKey string
	Token           string
	Expiration      time.Time
}

// Proxy provides HTTP handlers for responding to container requests and mediates requests
// to the real upstream metadata service. Its mediation duties also include mapping
// containers to the roles identified in their (docker) metadata, caching of
// container/credential information, and (optional) operational logging.
type Proxy struct {
	httpClient    http.RoundTripper
	credsProvider *credentialsProvider
	config        Config
	log           *log.Logger
}

// New creates a Proxy instance using the given configuration.
//
// logger := log.New(os.Stdout, "ec2metaproxy ", log.LstdFlags|log.LUTC)
// config := proxy.Config{ ... }
// p, err := proxy.New(config, sts.New(session.New()), logger)
//
func New(config Config, httpClient http.RoundTripper, stsSvc stsiface.STSAPI, containerSvc containerService, logger *log.Logger) (*Proxy, error) {
	if logger == nil {
		logger = log.New(new(nopWriter), "", log.LstdFlags)
	}

	var defaultIamRole roleArn
	var err error

	if config.DefaultAlias != "" {
		defaultIamRole, err = newRoleArn(config.AliasToARN[config.DefaultAlias])
		if err != nil {
			return nil, errors.Wrap(err, "Error configuring proxy")
		}
	}

	p := Proxy{
		credsProvider: newCredentialsProvider(stsSvc, containerSvc, defaultIamRole, config.DefaultPolicy),
		httpClient:    httpClient,
		log:           logger,
		config:        config,
	}

	return &p, nil
}

// ServeHTTP can be used to handle "/" requests and will delegate to HandleCredentials
// to produce a response.
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	clientIP := remoteIP(r.RemoteAddr)
	reqID := requestIDFromContext(r.Context())

	p.log.Printf("HandleCredentials (%s): PROXY REQUEST ip [%s] url [%s]", reqID, clientIP, r.URL.String())

	match := credsRegex.FindStringSubmatch(r.URL.Path)
	if match != nil {
		p.HandleCredentials(MetadataURL, match[1], match[2], p.credsProvider, w, r)
		return
	}

	if p.config.Verbose {
		p.log.Printf("ServeHTTP (%s): FORWARD REQUEST ip [%s] path [%s]", reqID, clientIP, r.URL.Path)
	}

	proxyReq, err := http.NewRequest(r.Method, fmt.Sprintf("%s%s", MetadataURL, r.URL.Path), r.Body)

	if err != nil {
		p.log.Printf("ServeHTTP (%s): Error creating proxy http request: %+v", reqID, err)
		http.Error(w, "An unexpected error occurred communicating with Amazon", http.StatusInternalServerError)
		return
	}

	copyHeaders(proxyReq.Header, r.Header)
	resp, err := p.httpClient.RoundTrip(proxyReq)

	if err != nil {
		p.log.Printf("ServeHTTP (%s): Error forwarding request to EC2 metadata service: %+v", reqID, err)
		http.Error(w, "An unexpected error occurred communicating with Amazon", http.StatusInternalServerError)
		return
	}

	defer func() {
		closeErr := resp.Body.Close()
		if closeErr != nil {
			p.log.Printf("ServeHTTP (%s): Error closing respond body: %+v", reqID, closeErr)
		}
	}()

	copyHeaders(w.Header(), resp.Header)
	w.WriteHeader(resp.StatusCode)
	if _, err := io.Copy(w, resp.Body); err != nil {
		p.log.Printf("ServeHTTP (%s): Error copying response content from EC2 metadata service: %+v", reqID, err)
	}

	if p.config.Verbose {
		p.log.Printf("ServeHTTP (%s): FORWARD RESPONSE ip [%s] path [%s]", reqID, clientIP, r.URL.Path)
	}
}

// HandleCredentials responds to credentials requests identified in ServeHTTP.
func (p *Proxy) HandleCredentials(baseURL, apiVersion, subpath string, c *credentialsProvider, w http.ResponseWriter, r *http.Request) {
	clientIP := remoteIP(r.RemoteAddr)
	ctx := r.Context()
	reqID := requestIDFromContext(ctx)
	awsURL := baseURL + "/" + apiVersion + "/meta-data/iam/security-credentials/"

	if p.config.Verbose {
		p.log.Printf("HandleCredentials (%s): UPSTREAM REQUEST ip [%s] path [%s]", reqID, clientIP, awsURL)
	}

	awsReq, err := http.NewRequest("GET", awsURL, nil)
	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error creating request [%s]: %+v", reqID, awsURL, err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	resp, err := p.httpClient.RoundTrip(awsReq)

	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error requesting creds path for API version [%s]: %+v", reqID, apiVersion, err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if p.config.Verbose {
		p.log.Printf("HandleCredentials (%s): UPSTREAM RESPONSE ip [%s] path [%s] code [%d]", reqID, clientIP, awsURL, resp.StatusCode)
	}

	err = resp.Body.Close()
	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error closing credentials response body: %+v", reqID, err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if resp.StatusCode != http.StatusOK {
		w.WriteHeader(resp.StatusCode)
		return
	}

	credentials, err := c.CredentialsForIP(ctx, clientIP)

	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error getting credentials for IP [%s]: %+v", reqID, clientIP, err)
		http.Error(w, "An unexpected error getting container role", http.StatusInternalServerError)
		return
	}

	roleName := credentials.RoleArn.RoleName()
	statusCode := http.StatusOK

	if len(subpath) == 0 {
		_, writeErr := w.Write([]byte(roleName))
		if writeErr != nil {
			p.log.Printf("HandleCredentials (%s): Error writing role name to response: %+v", reqID, writeErr)
		}
	} else if roleName == "" || (!strings.HasPrefix(subpath, roleName) || (len(subpath) > len(roleName) && subpath[len(roleName)-1] != '/')) {
		// An idiosyncrasy of the standard EC2 metadata service:
		// Subpaths of the role name are ignored. So long as the correct role name is provided,
		// it can be followed by a slash and anything after the slash is ignored.
		statusCode = http.StatusNotFound
		w.WriteHeader(statusCode)
	} else {
		creds, err := json.Marshal(&metadataCredentials{
			Code:            "Success",
			LastUpdated:     credentials.GeneratedAt,
			Type:            "AWS-HMAC",
			AccessKeyID:     credentials.AccessKey,
			SecretAccessKey: credentials.SecretKey,
			Token:           credentials.Token,
			Expiration:      credentials.Expiration,
		})

		if err != nil {
			p.log.Printf("HandleCredentials (%s): Error marshaling credentials: %+v", reqID, err)
			statusCode = http.StatusInternalServerError
			w.WriteHeader(statusCode)
		} else {
			_, writeErr := w.Write(creds)
			if writeErr != nil {
				p.log.Printf("HandleCredentials (%s): Error writing credentials to response: %+v", reqID, writeErr)
			}
		}
	}

	if p.config.Verbose {
		p.log.Printf("HandleCredentials (%s): PROXY RESPONSE ip [%s] path [%s] role [%s] code [%d]", reqID, clientIP, awsURL, roleName, statusCode)
	}
}

// Listen listens on the TCP address defined in the config file.
func (p *Proxy) Listen() error {
	err := http.ListenAndServe(p.config.ListenAddr, nil)
	if err == nil {
		p.log.Printf("Listen: [%s]", p.config.ListenAddr)
	} else {
		return errors.Wrapf(err, "Error listening on address [%s]", p.config.ListenAddr)
	}
	return nil
}
<commit_msg>feat: log subpath in response event<commit_after>package proxy

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/service/sts/stsiface"
	"github.com/pkg/errors"
)

var credsRegex = regexp.MustCompile("^/(.+?)/meta-data/iam/security-credentials/(.*)$")

type metadataCredentials struct {
	Code            string
	LastUpdated     time.Time
	Type            string
	AccessKeyID     string `json:"AccessKeyId"`
	SecretAccessKey string
	Token           string
	Expiration      time.Time
}

// Proxy provides HTTP handlers for responding to container requests and mediates requests
// to the real upstream metadata service. Its mediation duties also include mapping
// containers to the roles identified in their (docker) metadata, caching of
// container/credential information, and (optional) operational logging.
type Proxy struct {
	httpClient    http.RoundTripper
	credsProvider *credentialsProvider
	config        Config
	log           *log.Logger
}

// New creates a Proxy instance using the given configuration.
//
// logger := log.New(os.Stdout, "ec2metaproxy ", log.LstdFlags|log.LUTC)
// config := proxy.Config{ ... }
// p, err := proxy.New(config, sts.New(session.New()), logger)
//
func New(config Config, httpClient http.RoundTripper, stsSvc stsiface.STSAPI, containerSvc containerService, logger *log.Logger) (*Proxy, error) {
	if logger == nil {
		logger = log.New(new(nopWriter), "", log.LstdFlags)
	}

	var defaultIamRole roleArn
	var err error

	if config.DefaultAlias != "" {
		defaultIamRole, err = newRoleArn(config.AliasToARN[config.DefaultAlias])
		if err != nil {
			return nil, errors.Wrap(err, "Error configuring proxy")
		}
	}

	p := Proxy{
		credsProvider: newCredentialsProvider(stsSvc, containerSvc, defaultIamRole, config.DefaultPolicy),
		httpClient:    httpClient,
		log:           logger,
		config:        config,
	}

	return &p, nil
}

// ServeHTTP can be used to handle "/" requests and will delegate to HandleCredentials
// to produce a response.
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	clientIP := remoteIP(r.RemoteAddr)
	reqID := requestIDFromContext(r.Context())

	p.log.Printf("HandleCredentials (%s): PROXY REQUEST ip [%s] url [%s]", reqID, clientIP, r.URL.String())

	match := credsRegex.FindStringSubmatch(r.URL.Path)
	if match != nil {
		p.HandleCredentials(MetadataURL, match[1], match[2], p.credsProvider, w, r)
		return
	}

	if p.config.Verbose {
		p.log.Printf("ServeHTTP (%s): FORWARD REQUEST ip [%s] path [%s]", reqID, clientIP, r.URL.Path)
	}

	proxyReq, err := http.NewRequest(r.Method, fmt.Sprintf("%s%s", MetadataURL, r.URL.Path), r.Body)

	if err != nil {
		p.log.Printf("ServeHTTP (%s): Error creating proxy http request: %+v", reqID, err)
		http.Error(w, "An unexpected error occurred communicating with Amazon", http.StatusInternalServerError)
		return
	}

	copyHeaders(proxyReq.Header, r.Header)
	resp, err := p.httpClient.RoundTrip(proxyReq)

	if err != nil {
		p.log.Printf("ServeHTTP (%s): Error forwarding request to EC2 metadata service: %+v", reqID, err)
		http.Error(w, "An unexpected error occurred communicating with Amazon", http.StatusInternalServerError)
		return
	}

	defer func() {
		closeErr := resp.Body.Close()
		if closeErr != nil {
			p.log.Printf("ServeHTTP (%s): Error closing respond body: %+v", reqID, closeErr)
		}
	}()

	copyHeaders(w.Header(), resp.Header)
	w.WriteHeader(resp.StatusCode)
	if _, err := io.Copy(w, resp.Body); err != nil {
		p.log.Printf("ServeHTTP (%s): Error copying response content from EC2 metadata service: %+v", reqID, err)
	}

	if p.config.Verbose {
		p.log.Printf("ServeHTTP (%s): FORWARD RESPONSE ip [%s] path [%s]", reqID, clientIP, r.URL.Path)
	}
}

// HandleCredentials responds to credentials requests identified in ServeHTTP.
func (p *Proxy) HandleCredentials(baseURL, apiVersion, subpath string, c *credentialsProvider, w http.ResponseWriter, r *http.Request) {
	clientIP := remoteIP(r.RemoteAddr)
	ctx := r.Context()
	reqID := requestIDFromContext(ctx)
	awsURL := baseURL + "/" + apiVersion + "/meta-data/iam/security-credentials/"

	if p.config.Verbose {
		p.log.Printf("HandleCredentials (%s): UPSTREAM REQUEST ip [%s] path [%s]", reqID, clientIP, awsURL)
	}

	awsReq, err := http.NewRequest("GET", awsURL, nil)
	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error creating request [%s]: %+v", reqID, awsURL, err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	resp, err := p.httpClient.RoundTrip(awsReq)

	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error requesting creds path for API version [%s]: %+v", reqID, apiVersion, err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if p.config.Verbose {
		p.log.Printf("HandleCredentials (%s): UPSTREAM RESPONSE ip [%s] path [%s] code [%d]", reqID, clientIP, awsURL, resp.StatusCode)
	}

	err = resp.Body.Close()
	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error closing credentials response body: %+v", reqID, err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if resp.StatusCode != http.StatusOK {
		w.WriteHeader(resp.StatusCode)
		return
	}

	credentials, err := c.CredentialsForIP(ctx, clientIP)

	if err != nil {
		p.log.Printf("HandleCredentials (%s): Error getting credentials for IP [%s]: %+v", reqID, clientIP, err)
		http.Error(w, "An unexpected error getting container role", http.StatusInternalServerError)
		return
	}

	roleName := credentials.RoleArn.RoleName()
	statusCode := http.StatusOK

	if len(subpath) == 0 {
		_, writeErr := w.Write([]byte(roleName))
		if writeErr != nil {
			p.log.Printf("HandleCredentials (%s): Error writing role name to response: %+v", reqID, writeErr)
		}
	} else if roleName == "" || (!strings.HasPrefix(subpath, roleName) || (len(subpath) > len(roleName) && subpath[len(roleName)-1] != '/')) {
		// An idiosyncrasy of the standard EC2 metadata service:
		// Subpaths of the role name are ignored. So long as the correct role name is provided,
		// it can be followed by a slash and anything after the slash is ignored.
		statusCode = http.StatusNotFound
		w.WriteHeader(statusCode)
	} else {
		creds, err := json.Marshal(&metadataCredentials{
			Code:            "Success",
			LastUpdated:     credentials.GeneratedAt,
			Type:            "AWS-HMAC",
			AccessKeyID:     credentials.AccessKey,
			SecretAccessKey: credentials.SecretKey,
			Token:           credentials.Token,
			Expiration:      credentials.Expiration,
		})

		if err != nil {
			p.log.Printf("HandleCredentials (%s): Error marshaling credentials: %+v", reqID, err)
			statusCode = http.StatusInternalServerError
			w.WriteHeader(statusCode)
		} else {
			_, writeErr := w.Write(creds)
			if writeErr != nil {
				p.log.Printf("HandleCredentials (%s): Error writing credentials to response: %+v", reqID, writeErr)
			}
		}
	}

	if p.config.Verbose {
		p.log.Printf("HandleCredentials (%s): PROXY RESPONSE ip [%s] path [%s] role [%s] subpath [%s] code [%d]", reqID, clientIP, awsURL, roleName, subpath, statusCode)
	}
}

// Listen listens on the TCP address defined in the config file.
func (p *Proxy) Listen() error {
	err := http.ListenAndServe(p.config.ListenAddr, nil)
	if err == nil {
		p.log.Printf("Listen: [%s]", p.config.ListenAddr)
	} else {
		return errors.Wrapf(err, "Error listening on address [%s]", p.config.ListenAddr)
	}
	return nil
}
<|endoftext|>


<commit_before>package pgtest

import (
	"database/sql/driver"
	"runtime"
	"testing"

	"github.com/lib/pq"

	"chain/database/sql"
)

// WrapDB opens a new connection to the database at the provided URL,
// but with a driver that calls wrapFn on every driver.Stmt.Exec call
// and driver.Stmt.Query call.
//
// It also registers a finalizer for the DB, so callers can discard
// it without closing it explicitly.
func WrapDB(t testing.TB, url string, wrapFn func(string)) *sql.DB {
	// Register a new SQL driver that will wrapFn on every driver.Stmt
	// Exec and Query call.
	driverName := pickName("wrappeddriver")
	sql.Register(driverName, &wrappedDriver{fn: wrapFn})
	db, err := sql.Open(driverName, url)
	if err != nil {
		t.Fatal(err)
	}
	runtime.SetFinalizer(db, (*sql.DB).Close)
	return db
}

type wrappedDriver struct {
	fn func(string)
}

func (d *wrappedDriver) Open(name string) (driver.Conn, error) {
	conn, err := pq.Open(name)
	if err != nil {
		return conn, err
	}
	return wrappedConn{fn: d.fn, backing: conn}, nil
}

type wrappedConn struct {
	fn      func(string)
	backing driver.Conn
}

func (c wrappedConn) Prepare(query string) (driver.Stmt, error) {
	// TODO(jackson): Ideally, we could call fn() here, but if fn() panics
	// the system will deadlock. We call fn() from Stmt.Exec / Stmt.Query
	// to avoid the deadlock.
	//
	// This will be fixed in Go 1.8:
	// https://go-review.googlesource.com/#/c/23576/
	stmt, err := c.backing.Prepare(query)
	if err != nil {
		return stmt, err
	}
	return wrappedStmt{fn: c.fn, query: query, backing: stmt}, nil
}

func (c wrappedConn) Close() error {
	return c.backing.Close()
}

func (c wrappedConn) Begin() (driver.Tx, error) {
	return c.backing.Begin()
}

type wrappedStmt struct {
	fn      func(string)
	query   string
	backing driver.Stmt
}

func (s wrappedStmt) NumInput() int {
	return s.backing.NumInput()
}

func (s wrappedStmt) Close() error {
	return s.backing.Close()
}

func (s wrappedStmt) Exec(args []driver.Value) (driver.Result, error) {
	s.fn(s.query)
	return s.backing.Exec(args)
}

func (s wrappedStmt) Query(args []driver.Value) (driver.Rows, error) {
	s.fn(s.query)
	return s.backing.Query(args)
}
<commit_msg>database/pg/pgtest: invoke WrapDB func in Prepare<commit_after>package pgtest

import (
	"database/sql/driver"
	"runtime"
	"testing"

	"github.com/lib/pq"

	"chain/database/sql"
)

// WrapDB opens a new connection to the database at the provided URL,
// but with a driver that calls wrapFn on every driver.Stmt.Exec call
// and driver.Stmt.Query call.
//
// It also registers a finalizer for the DB, so callers can discard
// it without closing it explicitly.
func WrapDB(t testing.TB, url string, wrapFn func(string)) *sql.DB {
	// Register a new SQL driver that will wrapFn on every driver.Stmt
	// Exec and Query call.
	driverName := pickName("wrappeddriver")
	sql.Register(driverName, &wrappedDriver{fn: wrapFn})
	db, err := sql.Open(driverName, url)
	if err != nil {
		t.Fatal(err)
	}
	runtime.SetFinalizer(db, (*sql.DB).Close)
	return db
}

type wrappedDriver struct {
	fn func(string)
}

func (d *wrappedDriver) Open(name string) (driver.Conn, error) {
	conn, err := pq.Open(name)
	if err != nil {
		return conn, err
	}
	return wrappedConn{fn: d.fn, backing: conn}, nil
}

type wrappedConn struct {
	fn      func(string)
	backing driver.Conn
}

func (c wrappedConn) Prepare(query string) (driver.Stmt, error) {
	c.fn(query)
	stmt, err := c.backing.Prepare(query)
	if err != nil {
		return stmt, err
	}
	return stmt, nil
}

func (c wrappedConn) Close() error {
	return c.backing.Close()
}

func (c wrappedConn) Begin() (driver.Tx, error) {
	return c.backing.Begin()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"net"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	var port = flag.Int("p", 0, "port to listen on")
	flag.Parse()

	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		for range c {
			os.Exit(0)
		}
	}()

	ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
	if err != nil {
		fmt.Printf("failed to listen on port %d: %v\n", port, err)
		return
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("failed to accept connection:", err)
			continue
		}
		go handleConnection(conn)
	}
}

func handleConnection(c net.Conn) {
	var challenge uint32 = 0
	for {
		if err := binary.Read(c, binary.BigEndian, &challenge); err != nil {
			if err != io.EOF {
				fmt.Println("bad read", err)
			}
			return
		}
		challenge++
		if err := binary.Write(c, binary.BigEndian, &challenge); err != nil {
			fmt.Println("bad write", err)
			return
		}
	}
}
<commit_msg>Up gomaxprocs for go server<commit_after>package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"net"
	"os"
	"os/signal"
	"runtime"
	"syscall"
)

func main() {
	var port = flag.Int("p", 0, "port to listen on")
	flag.Parse()

	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		for range c {
			os.Exit(0)
		}
	}()

	runtime.GOMAXPROCS(runtime.NumCPU())
	ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
	if err != nil {
		fmt.Printf("failed to listen on port %d: %v\n", port, err)
		return
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("failed to accept connection:", err)
			continue
		}
		go handleConnection(conn)
	}
}

func handleConnection(c net.Conn) {
	var challenge uint32 = 0
	for {
		if err := binary.Read(c, binary.BigEndian, &challenge); err != nil {
			if err != io.EOF {
				fmt.Println("bad read", err)
			}
			return
		}
		challenge++
		if err := binary.Write(c, binary.BigEndian, &challenge); err != nil {
			fmt.Println("bad write", err)
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-vm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vmware

import (
	"github.com/go-vm/vmware/vmrun"
)

const fusionApp = "fusion"

// Fusion represents a VMware Fusion application.
type Fusion struct {
	vmx string
}

// NewFusion return the new Fusion.
func NewFusion(vmx string) *Fusion {
	return &Fusion{
		vmx: vmx,
	}
}

// Start start a VM or Team.
func (f *Fusion) Start(gui bool) error {
	return vmrun.Start(fusionApp, f.vmx, gui)
}

// ShutDown wrap of stop command with hard.
func (f *Fusion) ShutDown() error {
	return vmrun.Stop(fusionApp, f.vmx, true)
}

// Halt wrap of stop command with soft.
func (f *Fusion) Halt() error {
	return vmrun.Stop(fusionApp, f.vmx, false)
}

// Reset reset a VM or Team.
func (f *Fusion) Reset() error {
	return vmrun.Reset(fusionApp, f.vmx, true)
}

// Restart restart a VM uses wrap of reset command with soft.
func (f *Fusion) Restart() error {
	return vmrun.Reset(fusionApp, f.vmx, false)
}

// Suspend Suspend a VM or Team.
func (f *Fusion) Suspend(hard bool) error {
	return vmrun.Suspend(fusionApp, f.vmx, hard)
}

// Pause pause a VM.
func (f *Fusion) Pause() error {
	return vmrun.Pause(fusionApp, f.vmx)
}

// Unpause unpause a VM.
func (f *Fusion) Unpause(vmx string) error {
	return vmrun.Unpause(fusionApp, f.vmx)
}

// ListSnapshots list all snapshots in a VM.
func (f *Fusion) ListSnapshots() ([]string, int, error) {
	return vmrun.ListSnapshots(fusionApp, f.vmx)
}

// Snapshot create a snapshot of a VM.
func (f *Fusion) Snapshot(snapshotName string) error {
	return vmrun.Snapshot(fusionApp, f.vmx, snapshotName)
}

// DeleteSnapshot remove a snapshot from a VM.
func (f *Fusion) DeleteSnapshot(snapshotName string, deleteChildren bool) error {
	return vmrun.DeleteSnapshot(fusionApp, f.vmx, snapshotName, deleteChildren)
}

// RevertToSnapshot set VM state to a snapshot.
func (f *Fusion) RevertToSnapshot(snapshotName string) error {
	return vmrun.RevertToSnapshot(fusionApp, f.vmx, snapshotName)
}

// RunProgramInGuest run a program in Guest OS.
func (f *Fusion) RunProgramInGuest(username, password string, config vmrun.RunProgramInGuestConfig, cmdPath string, cmdArgs ...string) error {
	return vmrun.RunProgramInGuest(fusionApp, f.vmx, username, password, config, cmdPath, cmdArgs...)
}

// FileExistsInGuest check if a file exists in Guest OS.
func (f *Fusion) FileExistsInGuest(username, password string, filename string) bool {
	return vmrun.FileExistsInGuest(fusionApp, f.vmx, username, password, filename)
}

// DirectoryExistsInGuest check if a directory exists in Guest OS.
func (f *Fusion) DirectoryExistsInGuest(username, password string, dir string) bool {
	return vmrun.DirectoryExistsInGuest(fusionApp, f.vmx, username, password, dir)
}

// SetSharedFolderState modify a Host-Guest shared folder.
func (f *Fusion) SetSharedFolderState(shareName, hostPath string, writable bool) bool {
	return vmrun.SetSharedFolderState(fusionApp, f.vmx, shareName, hostPath, writable)
}
<commit_msg>fusion: add related SharedFolder functions<commit_after>// Copyright 2017 The go-vm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vmware

import (
	"github.com/go-vm/vmware/vmrun"
)

const fusionApp = "fusion"

// Fusion represents a VMware Fusion application.
type Fusion struct {
	vmx string
}

// NewFusion return the new Fusion.
func NewFusion(vmx string) *Fusion {
	return &Fusion{
		vmx: vmx,
	}
}

// Start start a VM or Team.
func (f *Fusion) Start(gui bool) error {
	return vmrun.Start(fusionApp, f.vmx, gui)
}

// ShutDown wrap of stop command with hard.
func (f *Fusion) ShutDown() error {
	return vmrun.Stop(fusionApp, f.vmx, true)
}

// Halt wrap of stop command with soft.
func (f *Fusion) Halt() error {
	return vmrun.Stop(fusionApp, f.vmx, false)
}

// Reset reset a VM or Team.
func (f *Fusion) Reset() error {
	return vmrun.Reset(fusionApp, f.vmx, true)
}

// Restart restart a VM uses wrap of reset command with soft.
func (f *Fusion) Restart() error {
	return vmrun.Reset(fusionApp, f.vmx, false)
}

// Suspend Suspend a VM or Team.
func (f *Fusion) Suspend(hard bool) error {
	return vmrun.Suspend(fusionApp, f.vmx, hard)
}

// Pause pause a VM.
func (f *Fusion) Pause() error {
	return vmrun.Pause(fusionApp, f.vmx)
}

// Unpause unpause a VM.
func (f *Fusion) Unpause(vmx string) error {
	return vmrun.Unpause(fusionApp, f.vmx)
}

// ListSnapshots list all snapshots in a VM.
func (f *Fusion) ListSnapshots() ([]string, int, error) {
	return vmrun.ListSnapshots(fusionApp, f.vmx)
}

// Snapshot create a snapshot of a VM.
func (f *Fusion) Snapshot(snapshotName string) error {
	return vmrun.Snapshot(fusionApp, f.vmx, snapshotName)
}

// DeleteSnapshot remove a snapshot from a VM.
func (f *Fusion) DeleteSnapshot(snapshotName string, deleteChildren bool) error {
	return vmrun.DeleteSnapshot(fusionApp, f.vmx, snapshotName, deleteChildren)
}

// RevertToSnapshot set VM state to a snapshot.
func (f *Fusion) RevertToSnapshot(snapshotName string) error {
	return vmrun.RevertToSnapshot(fusionApp, f.vmx, snapshotName)
}

// RunProgramInGuest run a program in Guest OS.
func (f *Fusion) RunProgramInGuest(username, password string, config vmrun.RunProgramInGuestConfig, cmdPath string, cmdArgs ...string) error {
	return vmrun.RunProgramInGuest(fusionApp, f.vmx, username, password, config, cmdPath, cmdArgs...)
}

// FileExistsInGuest check if a file exists in Guest OS.
func (f *Fusion) FileExistsInGuest(username, password string, filename string) bool {
	return vmrun.FileExistsInGuest(fusionApp, f.vmx, username, password, filename)
}

// DirectoryExistsInGuest check if a directory exists in Guest OS.
func (f *Fusion) DirectoryExistsInGuest(username, password string, dir string) bool {
	return vmrun.DirectoryExistsInGuest(fusionApp, f.vmx, username, password, dir)
}

// SetSharedFolderState modify a Host-Guest shared folder.
func (f *Fusion) SetSharedFolderState(shareName, hostPath string, writable bool) bool {
	return vmrun.SetSharedFolderState(fusionApp, f.vmx, shareName, hostPath, writable)
}

// AddSharedFolder add a Host-Guest shared folder.
func (f *Fusion) AddSharedFolder(shareName, newHostPath string) error {
	return vmrun.AddSharedFolder(fusionApp, f.vmx, shareName, newHostPath)
}

// RemoveSharedFolder remove a Host-Guest shared folder.
func (f *Fusion) RemoveSharedFolder(shareName string) error {
	return vmrun.RemoveSharedFolder(fusionApp, f.vmx, shareName)
}

// EnableSharedFolders enable shared folders in Guest.
func (f *Fusion) EnableSharedFolders(runtime bool) error {
	return vmrun.EnableSharedFolders(fusionApp, f.vmx, runtime)
}

// DisableSharedFolders disable shared folders in Guest.
func (f *Fusion) DisableSharedFolders(runtime bool) error {
	return vmrun.DisableSharedFolders(fusionApp, f.vmx, runtime)
}
<|endoftext|>


<commit_before>package ptt

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type Comment struct {
	PushTag        string
	PushUserID     string
	PushContent    string
	PushIpdatetime string
}

type Article struct {
	Title               string
	Author              string
	Date                string
	Content             string
	Ip                  string
	Comments            []Comment
	All, Count, P, B, N int
}

func NewArticle(url string) (*Article, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Cookie", "over18=1")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	doc, err := goquery.NewDocumentFromResponse(resp)
	if err != nil {
		return nil, err
	}
	a := &Article{}
	// check if 404 not found
	not_found := doc.Find("div.bbs-content").Text()
	if not_found == "404 - Not Found." {
		return nil, errors.New(fmt.Sprintf("Error: url %s not found", url))
	}
	// get selector of main content
	main_content := doc.Find("div#main-content")
	// get selector of article metaline
	main_content.Find("div.article-metaline").Each(func(i int, s *goquery.Selection) {
		k := s.Find("span.article-meta-tag").Text()
		v := s.Find("span.article-meta-value").Text()
		switch k {
		case "作者":
			a.Author = v
		case "標題":
			a.Title = v
		case "時間":
			a.Date = v
		}
		// remove article metaline
		s.Remove()
	})
	// remove remain article metaline
	main_content.Find("div.article-metaline-right").Each(func(i int, s *goquery.Selection) {
		s.Remove()
	})
	// get selector of pushes
	pushes := main_content.Find("div.push")
	a.Comments = make([]Comment, pushes.Size())
	pushes.Each(func(i int, push *goquery.Selection) {
		push_tag := strings.Trim(push.Find("span.push-tag").Text(), " \t\n\r")
		push_user_id := strings.Trim(push.Find("span.push-userid").Text(), " \t\n\r")
		push_content := strings.Trim(push.Find("span.push-content").Text(), ": \t\n\r")
		push_ipdatetime := strings.Trim(push.Find("span.push-ipdatetime").Text(), " \t\n\r")
		switch push_tag {
		case "推":
			a.P += 1
		case "噓":
			a.B += 1
		default:
			a.N += 1
		}
		a.Comments[i] = Comment{push_tag, push_user_id, push_content, push_ipdatetime}
		push.Remove()
	})
	// count: 推噓文相抵後的數量; all: 推文總數
	a.All = a.P + a.B + a.N
	a.Count = a.P - a.B
	// get ip
	html, err := main_content.Html()
	if err != nil {
		return nil, err
	}
	r, err := regexp.Compile("(※ 發信站: ).*")
	if err != nil {
		return nil, err
	}
	ip := r.FindString(html)
	r, err = regexp.Compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")
	if err != nil {
		return nil, err
	}
	a.Ip = r.FindString(ip)
	// remove redundant f2 class and remain text of others class
	main_content.Find("*").Each(func(i int, s *goquery.Selection) {
		text := s.Text()
		if strings.Contains(text, "※ 發信站:") || strings.Contains(text, "※ 文章網址:") || strings.Contains(text, "※ 編輯:") {
			s.Remove()
		} else {
			s.ReplaceWithHtml(text)
		}
	})
	content, err := main_content.Html()
	if err != nil {
		return nil, err
	}
	a.Content = strings.Trim(content, "- \t\n\r")
	return a, nil
}

func (c *Comment) String() string {
	return fmt.Sprintf("%q %q: %q\t%q", c.PushTag, c.PushUserID, c.PushContent, c.PushIpdatetime)
}

func (a *Article) String() string {
	var buffer bytes.Buffer
	meta := fmt.Sprintf("%q\n作者: %q, 日期: %q\n", a.Title, a.Author, a.Date)
	buffer.WriteString(meta)
	content := fmt.Sprintf("%q\n來源: %q\n", a.Content, a.Ip)
	buffer.WriteString(content)
	push_info := fmt.Sprintf("推文數: %v, 噓文數: %v, 其他: %v\n", a.P, a.B, a.N)
	buffer.WriteString(push_info)
	for _, c := range a.Comments {
		buffer.WriteString(fmt.Sprintf("%q\n", c))
	}
	return buffer.String()
}
<commit_msg>remove trimming blank char in content<commit_after>package ptt

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type Comment struct {
	PushTag        string
	PushUserID     string
	PushContent    string
	PushIpdatetime string
}

type Article struct {
	Title               string
	Author              string
	Date                string
	Content             string
	Ip                  string
	Comments            []Comment
	All, Count, P, B, N int
}

func NewArticle(url string) (*Article, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Cookie", "over18=1")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	doc, err := goquery.NewDocumentFromResponse(resp)
	if err != nil {
		return nil, err
	}
	a := &Article{}
	// check if 404 not found
	not_found := doc.Find("div.bbs-content").Text()
	if not_found == "404 - Not Found." {
		return nil, errors.New(fmt.Sprintf("Error: url %s not found", url))
	}
	// get selector of main content
	main_content := doc.Find("div#main-content")
	// get selector of article metaline
	main_content.Find("div.article-metaline").Each(func(i int, s *goquery.Selection) {
		k := s.Find("span.article-meta-tag").Text()
		v := s.Find("span.article-meta-value").Text()
		switch k {
		case "作者":
			a.Author = v
		case "標題":
			a.Title = v
		case "時間":
			a.Date = v
		}
		// remove article metaline
		s.Remove()
	})
	// remove remain article metaline
	main_content.Find("div.article-metaline-right").Each(func(i int, s *goquery.Selection) {
		s.Remove()
	})
	// get selector of pushes
	pushes := main_content.Find("div.push")
	a.Comments = make([]Comment, pushes.Size())
	pushes.Each(func(i int, push *goquery.Selection) {
		push_tag := strings.Trim(push.Find("span.push-tag").Text(), " \t\n\r")
		push_user_id := strings.Trim(push.Find("span.push-userid").Text(), " \t\n\r")
		push_content := strings.Trim(push.Find("span.push-content").Text(), ": \t\n\r")
		push_ipdatetime := strings.Trim(push.Find("span.push-ipdatetime").Text(), " \t\n\r")
		switch push_tag {
		case "推":
			a.P += 1
		case "噓":
			a.B += 1
		default:
			a.N += 1
		}
		a.Comments[i] = Comment{push_tag, push_user_id, push_content, push_ipdatetime}
		push.Remove()
	})
	// count: 推噓文相抵後的數量; all: 推文總數
	a.All = a.P + a.B + a.N
	a.Count = a.P - a.B
	// get ip
	html, err := main_content.Html()
	if err != nil {
		return nil, err
	}
	r, err := regexp.Compile("(※ 發信站: ).*")
	if err != nil {
		return nil, err
	}
	ip := r.FindString(html)
	r, err = regexp.Compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")
	if err != nil {
		return nil, err
	}
	a.Ip = r.FindString(ip)
	// remove redundant f2 class and remain text of others class
	main_content.Find("*").Each(func(i int, s *goquery.Selection) {
		text := s.Text()
		if strings.Contains(text, "※ 發信站:") || strings.Contains(text, "※ 文章網址:") || strings.Contains(text, "※ 編輯:") {
			s.Remove()
		} else {
			s.ReplaceWithHtml(text)
		}
	})
	content, err := main_content.Html()
	if err != nil {
		return nil, err
	}
	a.Content = strings.Trim(content, "-\t\n\r")
	return a, nil
}

func (c *Comment) String() string {
	return fmt.Sprintf("%q %q: %q\t%q", c.PushTag, c.PushUserID, c.PushContent, c.PushIpdatetime)
}

func (a *Article) String() string {
	var buffer bytes.Buffer
	meta := fmt.Sprintf("%q\n作者: %q, 日期: %q\n", a.Title, a.Author, a.Date)
	buffer.WriteString(meta)
	content := fmt.Sprintf("%q\n來源: %q\n", a.Content, a.Ip)
	buffer.WriteString(content)
	push_info := fmt.Sprintf("推文數: %v, 噓文數: %v, 其他: %v\n", a.P, a.B, a.N)
	buffer.WriteString(push_info)
	for _, c := range a.Comments {
		buffer.WriteString(fmt.Sprintf("%q\n", c))
	}
	return buffer.String()
}
<|endoftext|>


<commit_before>package service

import (
	"code.google.com/p/go-uuid/uuid"
	"fmt"
	"github.com/ninjasphere/app-presets/model"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	nmodel "github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/go-ninja/rpc"
	"strings"
	"time"
)

const defaultTimeout = 10 * time.Second

var excludedChannels []string = []string{}

type Connection interface {
	ExportService(service interface{}, topic string, ann *nmodel.ServiceAnnouncement) (*rpc.ExportedService, error)
	GetServiceClient(serviceTopic string) *ninja.ServiceClient
}

type PresetsService struct {
	Model       *model.Presets
	Save        func(*model.Presets)
	Conn        Connection
	Log         *logger.Logger
	initialized bool
}

func (ps *PresetsService) Init() error {
	if ps.Log == nil {
		return fmt.Errorf("illegal state: no logger")
	}
	if ps.Model == nil {
		return fmt.Errorf("illegal state: Model is nil")
	}
	if ps.Save == nil {
		return fmt.Errorf("illegal state: Save is nil")
	}
	if ps.Conn == nil {
		return fmt.Errorf("illegal state: Conn is nil")
	}

	var err error
	siteID := config.MustString("siteId")
	topic := fmt.Sprintf("$site/%s/service/%s", siteID, "presets")
	announcement := &nmodel.ServiceAnnouncement{
		Schema: "http://schema.ninjablocks.com/service/presets",
	}
	if _, err = ps.Conn.ExportService(ps, topic, announcement); err != nil {
		return err
	}

	ps.initialized = true
	return nil
}

func (ps *PresetsService) Destroy() error {
	ps.initialized = false
	return nil
}

func (ps *PresetsService) checkInit() {
	if ps.Log == nil {
		ps.Log = logger.GetLogger("com.ninja.app-presets")
	}
	if !ps.initialized {
		ps.Log.Fatalf("illegal state: the service is not initialized")
	}
}

// see: http://schema.ninjablocks.com/service/presets#fetchScenes
func (ps *PresetsService) FetchScenes(scope string) (*[]*model.Scene, error) {
	ps.checkInit()
	collect := make([]*model.Scene, 0, 0)
	for _, m := range ps.Model.Scenes {
		if m.Scope == scope {
			collect = append(collect, m)
		}
	}
	return &collect, nil
}

// see: http://schema.ninjablocks.com/service/presets#fetchScene
func (ps *PresetsService) FetchScene(id string) (*model.Scene, error) {
	ps.checkInit()
	for _, m := range ps.Model.Scenes {
		if m.ID == id {
			return m, nil
		}
	}
	return nil, fmt.Errorf("No such scene: %s", id)
}

func copyState(ch *nmodel.Channel) interface{} {
	if ch.LastState != nil {
		if state, ok := ch.LastState.(map[string]interface{}); ok {
			if payload, ok := state["payload"]; ok {
				return payload
			}
		}
	}
	return nil
}

// see: http://schema.ninjablocks.com/service/presets#fetchScenePrototype
func (ps *PresetsService) FetchScenePrototype(scope string) (*model.Scene, error) {
	ps.checkInit()

	location := ""
	roomOffset := strings.Index(scope, "room/")
	if roomOffset >= 0 {
		location = scope[roomOffset:]
	}
	thingClient := ps.Conn.GetServiceClient("$home/services/ThingModel")
	things := make([]*nmodel.Thing, 0)
	keptThings := make([]*nmodel.Thing, 0, len(things))
	if err := thingClient.Call("fetchAll", nil, &things, defaultTimeout); err != nil {
		return nil, err
	}

	for _, t := range things {
		if !t.Promoted ||
			(location != "" && (t.Location == nil || *t.Location != location)) {
			continue
		}
		keptThings = append(keptThings, t)
	}
	result := &model.Scene{
		Scope:  scope,
		Things: make([]model.ThingState, 0, len(keptThings)),
	}
	for _, t := range keptThings {
		if t.Device == nil || t.Device.Channels == nil {
			continue
		}
		thingState := model.ThingState{
			ID:       t.ID,
			Channels: make([]model.ChannelState, 0, len(*t.Device.Channels)),
		}
		channelIndex := make(map[string]int)
	Channels:
		for _, c := range *t.Device.Channels {

			for _, x := range excludedChannels {
				// don't include channels with excluded schema
				if x == c.Schema {
					continue Channels
				}
			}

			if c.SupportedMethods == nil {
				// don't include channels with no supported methods
				continue
			}

			found := false
			for _, m := range *c.SupportedMethods {
				found = (m == "set")
				if found {
					break
				}
			}
			if !found {
				// don't include channels that do not support the set method
				continue
			}
			channelState := model.ChannelState{
				ID: c.ID,
			}
			channelIndex[c.ID] = len(thingState.Channels)
			thingState.Channels = append(thingState.Channels, channelState)
		}

		// refresh the Thing to get the latest channel states

		refreshedThing := &nmodel.Thing{}
		if err := thingClient.Call("fetch", thingState.ID, &refreshedThing, defaultTimeout); err != nil {
			ps.Log.Errorf("error while refreshing thing %s: %v", thingState.ID, err)
			continue
		}
		if refreshedThing.Device == nil || refreshedThing.Device.Channels == nil {
			continue
		}

		for _, c := range *refreshedThing.Device.Channels {
			if x, ok := channelIndex[c.ID]; ok {
				thingState.Channels[x].State = copyState(c)
			}
		}

		// remove channels for which we don't have state.

		j := 0
		for i, c := range thingState.Channels {
			if c.State != nil {
				if i != j {
					thingState.Channels[j] = thingState.Channels[i]
				}
				j++
			} else {
				ps.Log.Debugf("Skipping channel %v of thing %v because no current state available", c.ID, thingState.ID)
			}
		}
		if j != len(thingState.Channels) {
			thingState.Channels = thingState.Channels[0:j]
		}

		if len(thingState.Channels) > 0 {
			result.Things = append(result.Things, thingState)
		}
	}
	return result, nil
}

// see: http://schema.ninjablocks.com/service/presets#storeScene
func (ps *PresetsService) StoreScene(model *model.Scene) (*model.Scene, error) {
	ps.checkInit()

	if model.Scope == "" {
		return nil, fmt.Errorf("illegal argument: model.Scope is empty")
	}

	if model.ID == "prototype" {
		return nil, fmt.Errorf("illegal argument: model.ID == \"prototype\"")
	}

	found := -1
	for i, m := range ps.Model.Scenes {
		if model.ID == "" {
			if m.Scope == model.Scope && m.Slot == model.Slot {
				found = i
				break
			}
		} else {
			if m.ID == model.ID {
				found = i
				break
			}
		}
	}

	if model.ID == "" {
		model.ID = uuid.NewUUID().String()
	}

	if found < 0 {
		ps.Model.Scenes = append(ps.Model.Scenes, model)
	} else {
		ps.Model.Scenes[found] = model
	}
	ps.Save(ps.Model)
	return model, nil
}

// see: http://schema.ninjablocks.com/service/presets#applyScene
func (ps *PresetsService) ApplyScene(id string) error {
	ps.checkInit()
	if _, err := ps.FetchScene(id); err != nil {
		return err
	} else {
		return nil
	}
}
<commit_msg>Remove unnecessary check.<commit_after>package service

import (
	"code.google.com/p/go-uuid/uuid"
	"fmt"
	"github.com/ninjasphere/app-presets/model"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	nmodel "github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/go-ninja/rpc"
	"strings"
	"time"
)

const defaultTimeout = 10 * time.Second

var excludedChannels []string = []string{}

type Connection interface {
	ExportService(service interface{}, topic string, ann *nmodel.ServiceAnnouncement) (*rpc.ExportedService, error)
	GetServiceClient(serviceTopic string) *ninja.ServiceClient
}

type PresetsService struct {
	Model       *model.Presets
	Save        func(*model.Presets)
	Conn        Connection
	Log         *logger.Logger
	initialized bool
}

func (ps *PresetsService) Init() error {
	if ps.Log == nil {
		return fmt.Errorf("illegal state: no logger")
	}
	if ps.Model == nil {
		return fmt.Errorf("illegal state: Model is nil")
	}
	if ps.Save == nil {
		return fmt.Errorf("illegal state: Save is nil")
	}
	if ps.Conn == nil {
		return fmt.Errorf("illegal state: Conn is nil")
	}

	var err error
	siteID := config.MustString("siteId")
	topic := fmt.Sprintf("$site/%s/service/%s", siteID, "presets")
	announcement := &nmodel.ServiceAnnouncement{
		Schema: "http://schema.ninjablocks.com/service/presets",
	}
	if _, err = ps.Conn.ExportService(ps, topic, announcement); err != nil {
		return err
	}

	ps.initialized = true
	return nil
}

func (ps *PresetsService) Destroy() error {
	ps.initialized = false
	return nil
}

func (ps *PresetsService) checkInit() {
	if ps.Log == nil {
		ps.Log = logger.GetLogger("com.ninja.app-presets")
	}
	if !ps.initialized {
		ps.Log.Fatalf("illegal state: the service is not initialized")
	}
}

// see: http://schema.ninjablocks.com/service/presets#fetchScenes
func (ps *PresetsService) FetchScenes(scope string) (*[]*model.Scene, error) {
	ps.checkInit()
	collect := make([]*model.Scene, 0, 0)
	for _, m := range ps.Model.Scenes {
		if m.Scope == scope {
			collect = append(collect, m)
		}
	}
	return &collect, nil
}

// see: http://schema.ninjablocks.com/service/presets#fetchScene
func (ps *PresetsService) FetchScene(id string) (*model.Scene, error) {
	ps.checkInit()
	for _, m := range ps.Model.Scenes {
		if m.ID == id {
			return m, nil
		}
	}
	return nil, fmt.Errorf("No such scene: %s", id)
}

func copyState(ch *nmodel.Channel) interface{} {
	if ch.LastState != nil {
		if state, ok := ch.LastState.(map[string]interface{}); ok {
			if payload, ok := state["payload"]; ok {
				return payload
			}
		}
	}
	return nil
}

// see: http://schema.ninjablocks.com/service/presets#fetchScenePrototype
func (ps *PresetsService) FetchScenePrototype(scope string) (*model.Scene, error) {
	ps.checkInit()

	location := ""
	roomOffset := strings.Index(scope, "room/")
	if roomOffset >= 0 {
		location = scope[roomOffset:]
	}
	thingClient := ps.Conn.GetServiceClient("$home/services/ThingModel")
	things := make([]*nmodel.Thing, 0)
	keptThings := make([]*nmodel.Thing, 0, len(things))
	if err := thingClient.Call("fetchAll", nil, &things, defaultTimeout); err != nil {
		return nil, err
	}

	for _, t := range things {
		if !t.Promoted ||
			(location != "" && (t.Location == nil || *t.Location != location)) {
			continue
		}
		keptThings = append(keptThings, t)
	}
	result := &model.Scene{
		Scope:  scope,
		Things: make([]model.ThingState, 0, len(keptThings)),
	}
	for _, t := range keptThings {
		if t.Device == nil || t.Device.Channels == nil {
			continue
		}
		thingState := model.ThingState{
			ID:       t.ID,
			Channels: make([]model.ChannelState, 0, len(*t.Device.Channels)),
		}
		channelIndex := make(map[string]int)
	Channels:
		for _, c := range *t.Device.Channels {

			for _, x := range excludedChannels {
				// don't include channels with excluded schema
				if x == c.Schema {
					continue Channels
				}
			}

			if c.SupportedMethods == nil {
				// don't include channels with no supported methods
				continue
			}

			found := false
			for _, m := range *c.SupportedMethods {
				found = (m == "set")
				if found {
					break
				}
			}
			if !found {
				// don't include channels that do not support the set method
				continue
			}
			channelState := model.ChannelState{
				ID: c.ID,
			}
			channelIndex[c.ID] = len(thingState.Channels)
			thingState.Channels = append(thingState.Channels, channelState)
		}

		// refresh the Thing to get the latest channel states

		refreshedThing := &nmodel.Thing{}
		if err := thingClient.Call("fetch", thingState.ID, &refreshedThing, defaultTimeout); err != nil {
			ps.Log.Errorf("error while refreshing thing %s: %v", thingState.ID, err)
			continue
		}
		if refreshedThing.Device == nil || refreshedThing.Device.Channels == nil {
			continue
		}

		for _, c := range *refreshedThing.Device.Channels {
			if x, ok := channelIndex[c.ID]; ok {
				thingState.Channels[x].State = copyState(c)
			}
		}

		// remove channels for which we don't have state.

		j := 0
		for i, c := range thingState.Channels {
			if c.State != nil {
				if i != j {
					thingState.Channels[j] = thingState.Channels[i]
				}
				j++
			} else {
				ps.Log.Debugf("Skipping channel %v of thing %v because no current state available", c.ID, thingState.ID)
			}
		}
		if j != len(thingState.Channels) {
			thingState.Channels = thingState.Channels[0:j]
		}

		if len(thingState.Channels) > 0 {
			result.Things = append(result.Things, thingState)
		}
	}
	return result, nil
}

// see: http://schema.ninjablocks.com/service/presets#storeScene
func (ps *PresetsService) StoreScene(model *model.Scene) (*model.Scene, error) {
	ps.checkInit()

	if model.Scope == "" {
		return nil, fmt.Errorf("illegal argument: model.Scope is empty")
	}

	found := -1
	for i, m := range ps.Model.Scenes {
		if model.ID == "" {
			if m.Scope == model.Scope && m.Slot == model.Slot {
				found = i
				break
			}
		} else {
			if m.ID == model.ID {
				found = i
				break
			}
		}
	}

	if model.ID == "" {
		model.ID = uuid.NewUUID().String()
	}

	if found < 0 {
		ps.Model.Scenes = append(ps.Model.Scenes, model)
	} else {
		ps.Model.Scenes[found] = model
	}
	ps.Save(ps.Model)
	return model, nil
}

// see: http://schema.ninjablocks.com/service/presets#applyScene
func (ps *PresetsService) ApplyScene(id string) error {
	ps.checkInit()
	if _, err := ps.FetchScene(id); err != nil {
		return err
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>package service

import (
	"errors"
	"net/http"

	"github.com/NYTimes/gizmo/config"
	"github.com/clawio/data/datacontroller"
	"github.com/clawio/sdk"
	"github.com/gorilla/context"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	// userKey can be used to store/retrieve a user ID in a request context.
	userKey contextKey = iota
)

type (
	// contextKey is a type to use as a key for storing data in the request context.
	contextKey int

	// Service implements server.Service and
	// handle all requests to the server.
	Service struct {
		Config         *Config
		SDK            *sdk.SDK
		DataController datacontroller.DataController
	}

	// Config is a struct that holds the
	// configuration for Service
	Config struct {
		Server         *config.Server
		General        *GeneralConfig
		DataController *DataControllerConfig
	}

	// GeneralConfig contains configuration parameters
	// for general parts of the service.
	GeneralConfig struct {
		AuthenticationServiceBaseURL string
		RequestBodyMaxSize           int64
	}

	// DataControllerConfig is a struct that holds
	// configuration parameters for a data controller.
	DataControllerConfig struct {
		Type                       string
		SimpleDataDir              string
		SimpleTempDir              string
		SimpleChecksum             string
		SimpleVerifyClientChecksum bool
	}
)

// New will instantiate and return
// a new Service that implements server.Service.
func New(cfg *Config) (*Service, error) {
	if cfg == nil {
		return nil, errors.New("config is nil")
	}
	if cfg.General == nil {
		return nil, errors.New("config.General is nil")
	}
	if cfg.DataController == nil {
		return nil, errors.New("config.DataController is  nil")
	}

	urls := &sdk.ServiceEndpoints{}
	urls.AuthServiceBaseURL = cfg.General.AuthenticationServiceBaseURL
	s := sdk.New(urls, nil)

	dataController := getDataController(cfg.DataController)
	return &Service{Config: cfg, SDK: s, DataController: dataController}, nil
}

func getDataController(cfg *DataControllerConfig) datacontroller.DataController {
	opts := &datacontroller.SimpleDataControllerOptions{
		DataDir:              cfg.SimpleDataDir,
		TempDir:              cfg.SimpleTempDir,
		Checksum:             cfg.SimpleChecksum,
		VerifyClientChecksum: cfg.SimpleVerifyClientChecksum,
	}
	return datacontroller.NewSimpleDataController(opts)
}

// Prefix returns the string prefix used for all endpoints within
// this service.
func (s *Service) Prefix() string {
	return "/clawio/v1/data"
}

// Middleware provides an http.Handler hook wrapped around all requests.
// In this implementation, we authenticate the request.
func (s *Service) Middleware(h http.Handler) http.Handler {
	return h
}

// Endpoints is a listing of all endpoints available in the Service.
func (s *Service) Endpoints() map[string]map[string]http.HandlerFunc {
	return map[string]map[string]http.HandlerFunc{
		"/metrics": map[string]http.HandlerFunc{
			"GET": func(w http.ResponseWriter, r *http.Request) {
				prometheus.Handler().ServeHTTP(w, r)
			},
		},
		"/upload/{path:.*}": map[string]http.HandlerFunc{
			"PUT": prometheus.InstrumentHandlerFunc("/upload", s.authenticateHandlerFunc(s.Upload)),
		},
		"/download/{path:.*}": map[string]http.HandlerFunc{
			"GET": prometheus.InstrumentHandlerFunc("/download", s.authenticateHandlerFunc(s.Download)),
		},
	}
}

func (s *Service) getTokenFromRequest(r *http.Request) string {
	if t := r.Header.Get("token"); t != "" {
		return t
	}
	return r.URL.Query().Get("token")
}

func (s *Service) authenticateHandlerFunc(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		token := s.getTokenFromRequest(r)
		user, _, err := s.SDK.Auth.Verify(token)
		if err != nil {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}
		context.Set(r, userKey, user)
		handler(w, r)
	}
}
<commit_msg>Fix formatting<commit_after>package service

import (
	"errors"
	"net/http"

	"github.com/NYTimes/gizmo/config"
	"github.com/clawio/data/datacontroller"
	"github.com/clawio/sdk"
	"github.com/gorilla/context"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	// userKey can be used to store/retrieve a user ID in a request context.
	userKey contextKey = iota
)

type (
	// contextKey is a type to use as a key for storing data in the request context.
	contextKey int

	// Service implements server.Service and
	// handle all requests to the server.
	Service struct {
		Config         *Config
		SDK            *sdk.SDK
		DataController datacontroller.DataController
	}

	// Config is a struct that holds the
	// configuration for Service
	Config struct {
		Server         *config.Server
		General        *GeneralConfig
		DataController *DataControllerConfig
	}

	// GeneralConfig contains configuration parameters
	// for general parts of the service.
	GeneralConfig struct {
		AuthenticationServiceBaseURL string
		RequestBodyMaxSize           int64
	}

	// DataControllerConfig is a struct that holds
	// configuration parameters for a data controller.
	DataControllerConfig struct {
		Type                       string
		SimpleDataDir              string
		SimpleTempDir              string
		SimpleChecksum             string
		SimpleVerifyClientChecksum bool
	}
)

// New will instantiate and return
// a new Service that implements server.Service.
func New(cfg *Config) (*Service, error) {
	if cfg == nil {
		return nil, errors.New("config is nil")
	}
	if cfg.General == nil {
		return nil, errors.New("config.General is nil")
	}
	if cfg.DataController == nil {
		return nil, errors.New("config.DataController is  nil")
	}

	urls := &sdk.ServiceEndpoints{}
	urls.AuthServiceBaseURL = cfg.General.AuthenticationServiceBaseURL
	s := sdk.New(urls, nil)

	dataController := getDataController(cfg.DataController)
	return &Service{Config: cfg, SDK: s, DataController: dataController}, nil
}

func getDataController(cfg *DataControllerConfig) datacontroller.DataController {
	opts := &datacontroller.SimpleDataControllerOptions{
		DataDir:              cfg.SimpleDataDir,
		TempDir:              cfg.SimpleTempDir,
		Checksum:             cfg.SimpleChecksum,
		VerifyClientChecksum: cfg.SimpleVerifyClientChecksum,
	}
	return datacontroller.NewSimpleDataController(opts)
}

// Prefix returns the string prefix used for all endpoints within
// this service.
func (s *Service) Prefix() string {
	return "/clawio/v1/data"
}

// Middleware provides an http.Handler hook wrapped around all requests.
// In this implementation, we authenticate the request.
func (s *Service) Middleware(h http.Handler) http.Handler {
	return h
}

// Endpoints is a listing of all endpoints available in the Service.
func (s *Service) Endpoints() map[string]map[string]http.HandlerFunc {
	return map[string]map[string]http.HandlerFunc{
		"/metrics": {
			"GET": func(w http.ResponseWriter, r *http.Request) {
				prometheus.Handler().ServeHTTP(w, r)
			},
		},
		"/upload/{path:.*}": {
			"PUT": prometheus.InstrumentHandlerFunc("/upload", s.authenticateHandlerFunc(s.Upload)),
		},
		"/download/{path:.*}": {
			"GET": prometheus.InstrumentHandlerFunc("/download", s.authenticateHandlerFunc(s.Download)),
		},
	}
}

func (s *Service) getTokenFromRequest(r *http.Request) string {
	if t := r.Header.Get("token"); t != "" {
		return t
	}
	return r.URL.Query().Get("token")
}

func (s *Service) authenticateHandlerFunc(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		token := s.getTokenFromRequest(r)
		user, _, err := s.SDK.Auth.Verify(token)
		if err != nil {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}
		context.Set(r, userKey, user)
		handler(w, r)
	}
}
<|endoftext|>


<commit_before>package service

import (
	"github.com/boivie/lovebeat-go/alert"
	"github.com/boivie/lovebeat-go/backend"
	"github.com/op/go-logging"
	"math"
	"regexp"
	"sort"
	"time"
)

const (
	MAX_UNPROCESSED_PACKETS = 1000
	EXPIRY_INTERVAL         = 1

	// Number of samples (diffs) we require to be able to
	// properly calculate an "auto" timeout
	AUTO_MIN_SAMPLES = 5
)

var (
	log          = logging.MustGetLogger("lovebeat")
	EMPTY_REGEXP = regexp.MustCompile("^$")
)

type Services struct {
	be                   backend.Backend
	alerters             []alert.Alerter
	services             map[string]*Service
	views                map[string]*View
	beatCmdChan          chan string
	upsertServiceCmdChan chan *upsertServiceCmd
	deleteServiceCmdChan chan string
	deleteViewCmdChan    chan string
	upsertViewCmdChan    chan *upsertViewCmd
	getServicesChan      chan *getServicesCmd
	getServiceChan       chan *getServiceCmd
	getViewsChan         chan *getViewsCmd
	getViewChan          chan *getViewCmd
}

type Service struct {
	svcs *Services
	data backend.StoredService
}

type View struct {
	svcs *Services
	data backend.StoredView
	ree  *regexp.Regexp
}

func now() int64 { return time.Now().Unix() }

func (s *Service) getExpiry(timeout int64) int64 {
	if timeout <= 0 {
		return 0
	}
	return s.data.LastBeat + timeout
}

func (s *Service) name() string { return s.data.Name }
func (v *View) name() string    { return v.data.Name }

func calcTimeout(values []int64) int64 {
	diffs := calcDiffs(values)
	if len(diffs) < AUTO_MIN_SAMPLES {
		log.Debug("AUTO-TIMEOUT: Not enough samples to calculate")
		return TIMEOUT_AUTO
	}

	ret := int64(math.Ceil(float64(median(diffs)) * 1.5))
	log.Debug("AUTO-TIMEOUT: vale calculated as %d", ret)
	return ret
}

func calcDiffs(values []int64) []int64 {
	var p []int64
	for i := 1; i < len(values); i++ {
		if values[i-1] != 0 && values[i] != 0 {
			p = append(p, values[i]-values[i-1])
		}
	}
	return p
}

type int64arr []int64

func (a int64arr) Len() int           { return len(a) }
func (a int64arr) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a int64arr) Less(i, j int) bool { return a[i] < a[j] }

func median(numbers []int64) int64 {
	sort.Sort(int64arr(numbers))
	middle := len(numbers) / 2
	result := numbers[middle]
	if len(numbers)%2 == 0 {
		result = (result + numbers[middle-1]) / 2
	}
	return result
}

// Called before saving - to update internal states
func (s *Service) update(ts int64) {
	s.data.State = s.stateAt(ts)

	if s.data.WarningTimeout == TIMEOUT_AUTO {
		s.data.WarningTimeout = calcTimeout(s.data.PreviousBeats)
	}
	if s.data.ErrorTimeout == TIMEOUT_AUTO {
		s.data.ErrorTimeout = calcTimeout(s.data.PreviousBeats)
	}
}

func (s *Service) stateAt(ts int64) string {
	var state = backend.STATE_OK
	var warningExpiry = s.getExpiry(s.data.WarningTimeout)
	var errorExpiry = s.getExpiry(s.data.ErrorTimeout)
	if warningExpiry > 0 && ts >= warningExpiry {
		state = backend.STATE_WARNING
	}
	if errorExpiry > 0 && ts >= errorExpiry {
		state = backend.STATE_ERROR
	}
	return state
}

func (s *Service) registerBeat(ts int64) {
	s.data.LastBeat = ts
	s.data.PreviousBeats = append(s.data.PreviousBeats[1:], ts)
}

func (s *Service) save(ref *Service, ts int64) {
	if s.data.State != ref.data.State {
		log.Info("SERVICE '%s', state %s -> %s",
			s.name(), ref.data.State, s.data.State)
	}
	if s.data.WarningTimeout != ref.data.WarningTimeout {
		log.Info("SERVICE '%s', warn %d -> %d",
			s.name(), ref.data.WarningTimeout,
			s.data.WarningTimeout)
	}
	if s.data.ErrorTimeout != ref.data.ErrorTimeout {
		log.Info("SERVICE '%s', err %d -> %d",
			s.name(), ref.data.ErrorTimeout,
			s.data.ErrorTimeout)
	}
	s.data.LastUpdated = ts
	s.svcs.be.SaveService(&s.data)
}

func (v *View) refresh(ts int64) {
	v.data.State = backend.STATE_OK
	for _, s := range v.svcs.services {
		if v.ree.Match([]byte(s.name())) {
			if s.data.State == backend.STATE_WARNING && v.data.State == backend.STATE_OK {
				v.data.State = backend.STATE_WARNING
			} else if s.data.State == backend.STATE_ERROR {
				v.data.State = backend.STATE_ERROR
			}
		}
	}
}

func (v *View) contains(serviceName string) bool {
	return v.ree.Match([]byte(serviceName))
}

func (v *View) save(ref *View, ts int64) {
	if v.data.State != ref.data.State {
		if ref.data.State == backend.STATE_OK {
			v.data.IncidentNbr += 1
		}
	}
	v.data.LastUpdated = ts
	v.svcs.be.SaveView(&v.data)
}

func (v *View) sendAlerts(ref *View, ts int64) {
	if v.data.State != ref.data.State {
		log.Info("VIEW '%s', %d: state %s -> %s",
			v.name(), v.data.IncidentNbr, ref.data.State,
			v.data.State)

		var services = make([]backend.StoredService, 0, 10)
		for _, s := range v.svcs.services {
			if (s.data.State == backend.STATE_WARNING ||
				s.data.State == backend.STATE_ERROR) &&
				v.contains(s.name()) {
				services = append(services, s.data)
				if len(services) == 10 {
					break
				}
			}
		}

		for _, a := range v.svcs.alerters {
			a.Notify(ref.data, v.data, services)
		}
	}
}

func (s *Service) updateViews(ts int64) {
	for _, view := range s.svcs.views {
		if view.ree.Match([]byte(s.name())) {
			var ref = *view
			view.refresh(ts)
			view.save(&ref, ts)
			view.sendAlerts(&ref, ts)
		}
	}
}

func (svcs *Services) getService(name string) *Service {
	var s, ok = svcs.services[name]
	if !ok {
		log.Debug("Asked for unknown service %s", name)
		s = &Service{
			svcs: svcs,
			data: backend.StoredService{
				Name:           name,
				LastValue:      -1,
				LastBeat:       -1,
				PreviousBeats:  make([]int64, backend.PREVIOUS_BEATS_COUNT),
				LastUpdated:    -1,
				WarningTimeout: -1,
				ErrorTimeout:   -1,
				State:          backend.STATE_PAUSED,
			},
		}
		svcs.services[name] = s
	}
	return s
}

func (svcs *Services) getView(name string) *View {
	var s, ok = svcs.views[name]
	if !ok {
		log.Debug("Asked for unknown view %s", name)
		s = &View{
			svcs: svcs,
			data: backend.StoredView{
				Name:        name,
				State:       backend.STATE_OK,
				LastUpdated: -1,
				Regexp:      "^$",
			},
			ree: EMPTY_REGEXP}
		svcs.views[name] = s
	}
	return s
}

func (svcs *Services) createView(name string, expr string, alertMail string,
	webhooks string, ts int64) {
	var ree, err = regexp.Compile(expr)
	if err != nil {
		log.Error("Invalid regexp: %s", err)
		return
	}

	var view = svcs.getView(name)
	var ref = *view
	view.data.Regexp = expr
	view.ree = ree
	view.data.AlertMail = alertMail
	view.data.Webhooks = webhooks
	view.refresh(ts)
	view.save(&ref, ts)

	log.Info("VIEW '%s' created or updated.", name)
}

func (svcs *Services) Monitor() {
	period := time.Duration(EXPIRY_INTERVAL) * time.Second
	ticker := time.NewTicker(period)
	for {
		select {
		case <-ticker.C:
			var ts = now()
			for _, s := range svcs.services {
				if s.data.State == backend.STATE_PAUSED || s.data.State == s.stateAt(ts) {
					continue
				}
				var ref = *s
				s.update(ts)
				s.save(&ref, ts)
				s.updateViews(ts)
			}
		case c := <-svcs.upsertViewCmdChan:
			log.Debug("Create or update view %s", c.View)
			svcs.createView(c.View, c.Regexp, c.AlertMail,
				c.Webhooks, now())
		case c := <-svcs.deleteViewCmdChan:
			log.Debug("Delete view %s", c)
			delete(svcs.views, c)
			svcs.be.DeleteView(c)
		case c := <-svcs.getServicesChan:
			var ret []backend.StoredService
			var view, ok = svcs.views[c.View]
			if ok {
				for _, s := range svcs.services {
					if view.contains(s.name()) {
						ret = append(ret, s.data)
					}
				}
			}
			c.Reply <- ret
		case c := <-svcs.getServiceChan:
			var ret = svcs.services[c.Name]
			c.Reply <- ret.data
		case c := <-svcs.getViewsChan:
			var ret []backend.StoredView
			for _, v := range svcs.views {
				ret = append(ret, v.data)
			}
			c.Reply <- ret
		case c := <-svcs.getViewChan:
			var ret = svcs.views[c.Name]
			c.Reply <- ret.data
		case c := <-svcs.beatCmdChan:
			var ts = now()
			var s = svcs.getService(c)
			var ref = *s
			s.registerBeat(ts)
			log.Debug("Beat from %s", s.name())
			s.update(ts)
			s.save(&ref, ts)
			s.updateViews(ts)
		case c := <-svcs.deleteServiceCmdChan:
			var ts = now()
			var s = svcs.getService(c)
			delete(s.svcs.services, s.name())
			s.svcs.be.DeleteService(s.name())
			s.updateViews(ts)
		case c := <-svcs.upsertServiceCmdChan:
			var ts = now()
			var s = svcs.getService(c.Service)
			var ref = *s
			// Don't re-calculate 'auto' if we already have values
			if c.WarningTimeout == TIMEOUT_AUTO &&
				s.data.WarningTimeout == -1 {
				s.data.WarningTimeout = TIMEOUT_AUTO
				s.data.PreviousBeats = make([]int64, backend.PREVIOUS_BEATS_COUNT)
			} else if c.WarningTimeout == TIMEOUT_CLEAR {
				s.data.WarningTimeout = TIMEOUT_CLEAR
			} else if c.WarningTimeout > 0 {
				s.data.WarningTimeout = c.WarningTimeout
			}
			if c.ErrorTimeout == TIMEOUT_AUTO &&
				s.data.ErrorTimeout == -1 {
				s.data.ErrorTimeout = TIMEOUT_AUTO
				s.data.PreviousBeats = make([]int64, backend.PREVIOUS_BEATS_COUNT)
			} else if c.ErrorTimeout == TIMEOUT_CLEAR {
				s.data.ErrorTimeout = TIMEOUT_CLEAR
			} else if c.ErrorTimeout > 0 {
				s.data.ErrorTimeout = c.ErrorTimeout
			}
			s.update(ts)
			s.save(&ref, ts)
			s.updateViews(ts)
		}
	}
}

func NewServices(beiface backend.Backend, alerters []alert.Alerter) *Services {
	svcs := new(Services)
	svcs.be = beiface
	svcs.alerters = alerters
	svcs.beatCmdChan = make(chan string, MAX_UNPROCESSED_PACKETS)
	svcs.deleteServiceCmdChan = make(chan string, 5)
	svcs.upsertServiceCmdChan = make(chan *upsertServiceCmd, 5)
	svcs.deleteViewCmdChan = make(chan string, 5)
	svcs.upsertViewCmdChan = make(chan *upsertViewCmd, 5)
	svcs.getServicesChan = make(chan *getServicesCmd, 5)
	svcs.getServiceChan = make(chan *getServiceCmd, 5)
	svcs.getViewsChan = make(chan *getViewsCmd, 5)
	svcs.getViewChan = make(chan *getViewCmd, 5)
	svcs.services = make(map[string]*Service)
	svcs.views = make(map[string]*View)

	for _, s := range svcs.be.LoadServices() {
		var svc = &Service{svcs: svcs, data: *s}
		if svc.data.PreviousBeats == nil || len(svc.data.PreviousBeats) != backend.PREVIOUS_BEATS_COUNT {
			svc.data.PreviousBeats = make([]int64, backend.PREVIOUS_BEATS_COUNT)
		}
		svcs.services[s.Name] = svc
	}

	for _, v := range svcs.be.LoadViews() {
		var ree, _ = regexp.Compile(v.Regexp)
		svcs.views[v.Name] = &View{svcs: svcs, data: *v, ree: ree}
	}

	return svcs
}
<commit_msg>Refactoring (2/10)<commit_after>package service

import (
	"github.com/boivie/lovebeat-go/alert"
	"github.com/boivie/lovebeat-go/backend"
	"github.com/op/go-logging"
	"math"
	"regexp"
	"sort"
	"time"
)

const (
	MAX_UNPROCESSED_PACKETS = 1000
	EXPIRY_INTERVAL         = 1

	// Number of samples (diffs) we require to be able to
	// properly calculate an "auto" timeout
	AUTO_MIN_SAMPLES = 5
)

var (
	log          = logging.MustGetLogger("lovebeat")
	EMPTY_REGEXP = regexp.MustCompile("^$")
)

type Services struct {
	be                   backend.Backend
	alerters             []alert.Alerter
	services             map[string]*Service
	views                map[string]*View
	beatCmdChan          chan string
	upsertServiceCmdChan chan *upsertServiceCmd
	deleteServiceCmdChan chan string
	deleteViewCmdChan    chan string
	upsertViewCmdChan    chan *upsertViewCmd
	getServicesChan      chan *getServicesCmd
	getServiceChan       chan *getServiceCmd
	getViewsChan         chan *getViewsCmd
	getViewChan          chan *getViewCmd
}

type Service struct {
	svcs *Services
	data backend.StoredService
}

type View struct {
	svcs *Services
	data backend.StoredView
	ree  *regexp.Regexp
}

func now() int64 { return time.Now().Unix() }

func (s *Service) getExpiry(timeout int64) int64 {
	if timeout <= 0 {
		return 0
	}
	return s.data.LastBeat + timeout
}

func (s *Service) name() string { return s.data.Name }
func (v *View) name() string    { return v.data.Name }

func calcTimeout(values []int64) int64 {
	diffs := calcDiffs(values)
	if len(diffs) < AUTO_MIN_SAMPLES {
		log.Debug("AUTO-TIMEOUT: Not enough samples to calculate")
		return TIMEOUT_AUTO
	}

	ret := int64(math.Ceil(float64(median(diffs)) * 1.5))
	log.Debug("AUTO-TIMEOUT: vale calculated as %d", ret)
	return ret
}

func calcDiffs(values []int64) []int64 {
	var p []int64
	for i := 1; i < len(values); i++ {
		if values[i-1] != 0 && values[i] != 0 {
			p = append(p, values[i]-values[i-1])
		}
	}
	return p
}

type int64arr []int64

func (a int64arr) Len() int           { return len(a) }
func (a int64arr) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a int64arr) Less(i, j int) bool { return a[i] < a[j] }

func median(numbers []int64) int64 {
	sort.Sort(int64arr(numbers))
	middle := len(numbers) / 2
	result := numbers[middle]
	if len(numbers)%2 == 0 {
		result = (result + numbers[middle-1]) / 2
	}
	return result
}

// Called before saving - to update internal states
func (s *Service) update(ts int64) {
	s.data.State = s.stateAt(ts)

	if s.data.WarningTimeout == TIMEOUT_AUTO {
		s.data.WarningTimeout = calcTimeout(s.data.PreviousBeats)
	}
	if s.data.ErrorTimeout == TIMEOUT_AUTO {
		s.data.ErrorTimeout = calcTimeout(s.data.PreviousBeats)
	}
}

func (s *Service) stateAt(ts int64) string {
	var state = backend.STATE_OK
	var warningExpiry = s.getExpiry(s.data.WarningTimeout)
	var errorExpiry = s.getExpiry(s.data.ErrorTimeout)
	if warningExpiry > 0 && ts >= warningExpiry {
		state = backend.STATE_WARNING
	}
	if errorExpiry > 0 && ts >= errorExpiry {
		state = backend.STATE_ERROR
	}
	return state
}

func (s *Service) registerBeat(ts int64) {
	s.data.LastBeat = ts
	s.data.PreviousBeats = append(s.data.PreviousBeats[1:], ts)
}

func (s *Service) save(ref *Service, ts int64) {
	if s.data.State != ref.data.State {
		log.Info("SERVICE '%s', state %s -> %s",
			s.name(), ref.data.State, s.data.State)
	}
	if s.data.WarningTimeout != ref.data.WarningTimeout {
		log.Info("SERVICE '%s', warn %d -> %d",
			s.name(), ref.data.WarningTimeout,
			s.data.WarningTimeout)
	}
	if s.data.ErrorTimeout != ref.data.ErrorTimeout {
		log.Info("SERVICE '%s', err %d -> %d",
			s.name(), ref.data.ErrorTimeout,
			s.data.ErrorTimeout)
	}
	s.data.LastUpdated = ts
	s.svcs.be.SaveService(&s.data)
}

func (v *View) refresh(ts int64) {
	v.data.State = backend.STATE_OK
	for _, s := range v.svcs.services {
		if v.ree.Match([]byte(s.name())) {
			if s.data.State == backend.STATE_WARNING && v.data.State == backend.STATE_OK {
				v.data.State = backend.STATE_WARNING
			} else if s.data.State == backend.STATE_ERROR {
				v.data.State = backend.STATE_ERROR
			}
		}
	}
}

func (v *View) contains(serviceName string) bool {
	return v.ree.Match([]byte(serviceName))
}

func (v *View) save(ref *View, ts int64) {
	if v.data.State != ref.data.State {
		if ref.data.State == backend.STATE_OK {
			v.data.IncidentNbr += 1
		}
	}
	v.data.LastUpdated = ts
	v.svcs.be.SaveView(&v.data)
}

func (v *View) sendAlerts(ref *View, ts int64) {
	if v.data.State != ref.data.State {
		log.Info("VIEW '%s', %d: state %s -> %s",
			v.name(), v.data.IncidentNbr, ref.data.State,
			v.data.State)

		var services = make([]backend.StoredService, 0, 10)
		for _, s := range v.svcs.services {
			if (s.data.State == backend.STATE_WARNING ||
				s.data.State == backend.STATE_ERROR) &&
				v.contains(s.name()) {
				services = append(services, s.data)
				if len(services) == 10 {
					break
				}
			}
		}

		for _, a := range v.svcs.alerters {
			a.Notify(ref.data, v.data, services)
		}
	}
}

func (s *Service) updateViews(ts int64) {
	for _, view := range s.svcs.views {
		if view.ree.Match([]byte(s.name())) {
			var ref = *view
			view.refresh(ts)
			view.save(&ref, ts)
			view.sendAlerts(&ref, ts)
		}
	}
}

func (svcs *Services) getService(name string) *Service {
	var s, ok = svcs.services[name]
	if !ok {
		log.Debug("Asked for unknown service %s", name)
		s = &Service{
			svcs: svcs,
			data: backend.StoredService{
				Name:           name,
				LastValue:      -1,
				LastBeat:       -1,
				PreviousBeats:  make([]int64, backend.PREVIOUS_BEATS_COUNT),
				LastUpdated:    -1,
				WarningTimeout: -1,
				ErrorTimeout:   -1,
				State:          backend.STATE_PAUSED,
			},
		}
		svcs.services[name] = s
	}
	return s
}

func (svcs *Services) getView(name string) *View {
	var s, ok = svcs.views[name]
	if !ok {
		log.Debug("Asked for unknown view %s", name)
		s = &View{
			svcs: svcs,
			data: backend.StoredView{
				Name:        name,
				State:       backend.STATE_OK,
				LastUpdated: -1,
				Regexp:      "^$",
			},
			ree: EMPTY_REGEXP}
		svcs.views[name] = s
	}
	return s
}

func (svcs *Services) createView(name string, expr string, alertMail string,
	webhooks string, ts int64) {
	var ree, err = regexp.Compile(expr)
	if err != nil {
		log.Error("Invalid regexp: %s", err)
		return
	}

	var view = svcs.getView(name)
	var ref = *view
	view.data.Regexp = expr
	view.ree = ree
	view.data.AlertMail = alertMail
	view.data.Webhooks = webhooks
	view.refresh(ts)
	view.save(&ref, ts)

	log.Info("VIEW '%s' created or updated.", name)
}

func (svcs *Services) Monitor() {
	period := time.Duration(EXPIRY_INTERVAL) * time.Second
	ticker := time.NewTicker(period)
	for {
		select {
		case <-ticker.C:
			var ts = now()
			for _, s := range svcs.services {
				if s.data.State == backend.STATE_PAUSED || s.data.State == s.stateAt(ts) {
					continue
				}
				var ref = *s
				s.update(ts)
				s.save(&ref, ts)
				s.updateViews(ts)
			}
		case c := <-svcs.upsertViewCmdChan:
			log.Debug("Create or update view %s", c.View)
			svcs.createView(c.View, c.Regexp, c.AlertMail,
				c.Webhooks, now())
		case c := <-svcs.deleteViewCmdChan:
			log.Debug("Delete view %s", c)
			delete(svcs.views, c)
			svcs.be.DeleteView(c)
		case c := <-svcs.getServicesChan:
			var ret []backend.StoredService
			var view, ok = svcs.views[c.View]
			if ok {
				for _, s := range svcs.services {
					if view.contains(s.name()) {
						ret = append(ret, s.data)
					}
				}
			}
			c.Reply <- ret
		case c := <-svcs.getServiceChan:
			var ret = svcs.services[c.Name]
			c.Reply <- ret.data
		case c := <-svcs.getViewsChan:
			var ret []backend.StoredView
			for _, v := range svcs.views {
				ret = append(ret, v.data)
			}
			c.Reply <- ret
		case c := <-svcs.getViewChan:
			var ret = svcs.views[c.Name]
			c.Reply <- ret.data
		case c := <-svcs.beatCmdChan:
			var ts = now()
			var s = svcs.getService(c)
			var ref = *s
			s.registerBeat(ts)
			log.Debug("Beat from %s", s.name())
			s.update(ts)
			s.save(&ref, ts)
			s.updateViews(ts)
		case c := <-svcs.deleteServiceCmdChan:
			var ts = now()
			var s = svcs.getService(c)
			delete(svcs.services, s.name())
			svcs.be.DeleteService(s.name())
			s.updateViews(ts)
		case c := <-svcs.upsertServiceCmdChan:
			var ts = now()
			var s = svcs.getService(c.Service)
			var ref = *s
			// Don't re-calculate 'auto' if we already have values
			if c.WarningTimeout == TIMEOUT_AUTO &&
				s.data.WarningTimeout == -1 {
				s.data.WarningTimeout = TIMEOUT_AUTO
				s.data.PreviousBeats = make([]int64, backend.PREVIOUS_BEATS_COUNT)
			} else if c.WarningTimeout == TIMEOUT_CLEAR {
				s.data.WarningTimeout = TIMEOUT_CLEAR
			} else if c.WarningTimeout > 0 {
				s.data.WarningTimeout = c.WarningTimeout
			}
			if c.ErrorTimeout == TIMEOUT_AUTO &&
				s.data.ErrorTimeout == -1 {
				s.data.ErrorTimeout = TIMEOUT_AUTO
				s.data.PreviousBeats = make([]int64, backend.PREVIOUS_BEATS_COUNT)
			} else if c.ErrorTimeout == TIMEOUT_CLEAR {
				s.data.ErrorTimeout = TIMEOUT_CLEAR
			} else if c.ErrorTimeout > 0 {
				s.data.ErrorTimeout = c.ErrorTimeout
			}
			s.update(ts)
			s.save(&ref, ts)
			s.updateViews(ts)
		}
	}
}

func NewServices(beiface backend.Backend, alerters []alert.Alerter) *Services {
	svcs := new(Services)
	svcs.be = beiface
	svcs.alerters = alerters
	svcs.beatCmdChan = make(chan string, MAX_UNPROCESSED_PACKETS)
	svcs.deleteServiceCmdChan = make(chan string, 5)
	svcs.upsertServiceCmdChan = make(chan *upsertServiceCmd, 5)
	svcs.deleteViewCmdChan = make(chan string, 5)
	svcs.upsertViewCmdChan = make(chan *upsertViewCmd, 5)
	svcs.getServicesChan = make(chan *getServicesCmd, 5)
	svcs.getServiceChan = make(chan *getServiceCmd, 5)
	svcs.getViewsChan = make(chan *getViewsCmd, 5)
	svcs.getViewChan = make(chan *getViewCmd, 5)
	svcs.services = make(map[string]*Service)
	svcs.views = make(map[string]*View)

	for _, s := range svcs.be.LoadServices() {
		var svc = &Service{svcs: svcs, data: *s}
		if svc.data.PreviousBeats == nil || len(svc.data.PreviousBeats) != backend.PREVIOUS_BEATS_COUNT {
			svc.data.PreviousBeats = make([]int64, backend.PREVIOUS_BEATS_COUNT)
		}
		svcs.services[s.Name] = svc
	}

	for _, v := range svcs.be.LoadViews() {
		var ree, _ = regexp.Compile(v.Regexp)
		svcs.views[v.Name] = &View{svcs: svcs, data: *v, ree: ree}
	}

	return svcs
}
<|endoftext|>


<commit_before>package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/SudoQ/ganache/item"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"
)

type Service struct {
	URL    string        `json:"url"`
	Port   string        `json:"port"`
	Period time.Duration `json:"-"`
	Limit  uint          `json:"limit"`
	Cache  *item.Item    `json:"-"`
	logger *log.Logger   `json:"-"`
}

func New(url, port string, limit uint) *Service {
	period, err := LimitToPeriod(limit)
	if err != nil {
		limit = 1
		period, _ = LimitToPeriod(limit)
	}
	srv := &Service{
		URL:    url,
		Port:   port,
		Period: period,
		Limit:  limit,
		Cache:  nil,
		logger: nil,
	}
	srv.initLogger()
	return srv
}

func (this *Service) initLogger() {
	this.logger = log.New(os.Stdin, fmt.Sprintf("[ganache]"), log.Lshortfile)
}

func LimitToPeriod(limit uint) (time.Duration, error) {
	if limit == 0 {
		return 0, errors.New("Division with zero")
	}

	period := (60.0/float64(limit))*60
	return (time.Duration(period) * time.Second), nil
}

func NewFromJSON(jsonBlob []byte) (*Service, error) {
	var srv Service
	err := json.Unmarshal(jsonBlob, &srv)
	if err != nil {
		return nil, err
	}
	srv.Period, err = LimitToPeriod(srv.Limit)
	if err != nil {
		srv.Limit = 1
		srv.Period, _ = LimitToPeriod(srv.Limit)
	}
	srv.initLogger()
	return &srv, nil
}

func NewFromFile(filename string) (*Service, error) {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return NewFromJSON(jsonBlob)
}

func (this *Service) LoadConfig(filename string) error {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = json.Unmarshal(jsonBlob, this)
	this.initLogger()
	return err
}

func (this *Service) JSON() ([]byte, error) {
	blob, err := json.Marshal(this)
	if err != nil {
		return nil, err
	}
	return blob, nil
}

func (this *Service) SaveConfig(filename string) error {
	jsonBlob, err := this.JSON()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, jsonBlob, 0644)
	return err
}

func (this *Service) Collect() {
	for {
		func(){
			now := time.Now()
			timeDelta := now.Sub(this.Cache.Timestamp)
			if this.Cache.Timestamp.Before(now) && timeDelta > this.Period {
				this.logger.Printf("GET %s\n", this.URL)

				resp, err := http.Get(this.URL)
				if err != nil {
					this.logger.Println(err)
					return
				}

				payload, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					this.logger.Println(err)
					return
				}

				// TODO Store header info?
				newItem := item.New(now, payload)
				this.Cache = newItem
				cacheFilename := "cache.json"
				err = this.Cache.WriteFile(cacheFilename)
				if err != nil {
					this.logger.Fatal(err)
				}
				this.logger.Printf("Saved cache to %s\n", cacheFilename)

			}
		}()
		select {
		case <-time.After(this.Period):
			continue
		}
	}
}

func (this *Service) LoadCache(filename string) error {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	newItem, err := item.NewFromJSON(content)
	if err != nil {
		return err
	}
	this.Cache = newItem
	return nil
}

func (this *Service) Info() string {
	return fmt.Sprintf("Ganache API caching service v0.1")
}

func (this *Service) Run() {
	err := this.LoadCache("cache.json")
	if err != nil {
		this.logger.Fatal(err)
	}
	go this.Collect()
	r := mux.NewRouter()
	r.HandleFunc("/", this.HomeHandler)
	r.HandleFunc("/info", this.InfoHandler)
	r.HandleFunc("/cache.json", this.CacheHandler)
	port := fmt.Sprintf(":%s", this.Port)
	http.ListenAndServe(port, r)
}

func (this *Service) HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(this.Cache.Payload)
}

func (this *Service) InfoHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)
	w.Write([]byte(this.Info()))
}

func (this *Service) CacheHandler(w http.ResponseWriter, r *http.Request) {
	response, err := this.Cache.JSON()
	if err != nil {
		this.logger.Println(err)
		w.WriteHeader(404)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}
<commit_msg>Added label to the logger function according to the host url<commit_after>package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/SudoQ/ganache/item"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"
	"net/url"
)

type Service struct {
	URL    string        `json:"url"`
	Port   string        `json:"port"`
	Period time.Duration `json:"-"`
	Limit  uint          `json:"limit"`
	Cache  *item.Item    `json:"-"`
	logger *log.Logger   `json:"-"`
}

func New(targetUrl, port string, limit uint) *Service {
	period, err := LimitToPeriod(limit)
	if err != nil {
		limit = 1
		period, _ = LimitToPeriod(limit)
	}
	srv := &Service{
		URL:    targetUrl,
		Port:   port,
		Period: period,
		Limit:  limit,
		Cache:  nil,
		logger: nil,
	}
	srv.initLogger()
	return srv
}

func (this *Service) initLogger() {
	u, err := url.Parse(this.URL)
	label := u.Host
	if err != nil{
		label = "?"
	}
	this.logger = log.New(os.Stdin, fmt.Sprintf("ganache[%s]: ", label), log.Lshortfile)
}

func LimitToPeriod(limit uint) (time.Duration, error) {
	if limit == 0 {
		return 0, errors.New("Division with zero")
	}

	period := (60.0/float64(limit))*60
	return (time.Duration(period) * time.Second), nil
}

func NewFromJSON(jsonBlob []byte) (*Service, error) {
	var srv Service
	err := json.Unmarshal(jsonBlob, &srv)
	if err != nil {
		return nil, err
	}
	srv.Period, err = LimitToPeriod(srv.Limit)
	if err != nil {
		srv.Limit = 1
		srv.Period, _ = LimitToPeriod(srv.Limit)
	}
	srv.initLogger()
	return &srv, nil
}

func NewFromFile(filename string) (*Service, error) {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return NewFromJSON(jsonBlob)
}

func (this *Service) LoadConfig(filename string) error {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = json.Unmarshal(jsonBlob, this)
	this.initLogger()
	return err
}

func (this *Service) JSON() ([]byte, error) {
	blob, err := json.Marshal(this)
	if err != nil {
		return nil, err
	}
	return blob, nil
}

func (this *Service) SaveConfig(filename string) error {
	jsonBlob, err := this.JSON()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, jsonBlob, 0644)
	return err
}

func (this *Service) Collect() {
	for {
		func(){
			now := time.Now()
			timeDelta := now.Sub(this.Cache.Timestamp)
			if this.Cache.Timestamp.Before(now) && timeDelta > this.Period {
				this.logger.Printf("GET %s\n", this.URL)

				resp, err := http.Get(this.URL)
				if err != nil {
					this.logger.Println(err)
					return
				}

				payload, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					this.logger.Println(err)
					return
				}

				// TODO Store header info?
				newItem := item.New(now, payload)
				this.Cache = newItem
				cacheFilename := "cache.json"
				err = this.Cache.WriteFile(cacheFilename)
				if err != nil {
					this.logger.Fatal(err)
				}
				this.logger.Printf("Saved cache to %s\n", cacheFilename)

			}
		}()
		select {
		case <-time.After(this.Period):
			continue
		}
	}
}

func (this *Service) LoadCache(filename string) error {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	newItem, err := item.NewFromJSON(content)
	if err != nil {
		return err
	}
	this.Cache = newItem
	return nil
}

func (this *Service) Info() string {
	return fmt.Sprintf("Ganache API caching service v0.1")
}

func (this *Service) Run() {
	err := this.LoadCache("cache.json")
	if err != nil {
		this.logger.Fatal(err)
	}
	go this.Collect()
	r := mux.NewRouter()
	r.HandleFunc("/", this.HomeHandler)
	r.HandleFunc("/info", this.InfoHandler)
	r.HandleFunc("/cache.json", this.CacheHandler)
	port := fmt.Sprintf(":%s", this.Port)
	http.ListenAndServe(port, r)
}

func (this *Service) HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(this.Cache.Payload)
}

func (this *Service) InfoHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)
	w.Write([]byte(this.Info()))
}

func (this *Service) CacheHandler(w http.ResponseWriter, r *http.Request) {
	response, err := this.Cache.JSON()
	if err != nil {
		this.logger.Println(err)
		w.WriteHeader(404)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}
<|endoftext|>


<commit_before>package endly

import (
	"fmt"
	"github.com/viant/endly/common"
	"github.com/viant/toolbox"
	"strings"
	"time"
)

const ProcessServiceId = "process"
const processesKey = "pid"

type ProcessStartRequest struct {
	Name          string
	Target        *Resource
	Options       *ExecutionOptions
	SystemService bool
	Directory     string
	Command       string
	Arguments     []string
}

type ProcessStartResponse struct {
	Command string
	Info []*ProcessInfo
}

type ProcessInfo struct {
	Name      string
	Pid       int
	Command   string
	Arguments []string
	Stdin     string
	Stdout    string
}

type ProcessStatusRequest struct {
	Target  *Resource
	Command string
}

type ProcessStopRequest struct {
	Target *Resource
	Pid    int
}

type processService struct {
	*AbstractService
}

func (s *processService) Run(context *Context, request interface{}) *ServiceResponse {
	var response = &ServiceResponse{Status: "ok"}
	var err error
	switch actualRequest := request.(type) {
	case *ProcessStartRequest:
		response.Response, err = s.startProcess(context, actualRequest)
		if err != nil {
			response.Error = fmt.Sprintf("Failed to start process: %v, %v", actualRequest.Name, err)
		}
	case *ProcessStopRequest:
		response.Response, err = s.stopProcess(context, actualRequest)
		if err != nil {
			response.Error = fmt.Sprintf("Failed to stop process: %v, %v", actualRequest.Pid, err)
		}

	}
	if response.Error != "" {
		response.Status = "err"
	}
	return response
}

func (s *processService) checkProcess(context *Context, request *ProcessStatusRequest) ([]*ProcessInfo, error) {
	commandResponse, err := context.Execute(request.Target, &ManagedCommand{
		Executions: []*Execution{
			{
				Command: "ps -ev | grep " + request.Command,
			},
		},
	})

	if err != nil {
		return nil, err
	}

	var state = context.State()
	if !state.Has(processesKey) {
		state.Put(processesKey, common.NewMap())
	}
	var processes = state.GetMap(processesKey)
	processes.Put(request.Command, 0)
	var result = make([]*ProcessInfo, 0)

	for _, line := range strings.Split(commandResponse.Stdout(), "\r\n") {
		if strings.Contains(line, "grep") {
			continue
		}
		pid, ok := ExtractColumn(line, 0)
		if !ok {
			continue
		}
		argumentsIndex := strings.Index(line, request.Command)
		var arguments []string
		if argumentsIndex != -1 {
			args := strings.Trim(line[argumentsIndex+len(request.Command)+1:], " &\t")
			arguments = strings.Split(args, " ")
		}
		info := &ProcessInfo{
			Pid:       toolbox.AsInt(pid),
			Command:   request.Command,
			Arguments: arguments,
			Stdout:    line,
		}
		result = append(result, info)
		processes.Put(request.Command, info.Pid)
	}
	return result, nil
}

func (s *processService) stopProcess(context *Context, request *ProcessStopRequest) (*CommandInfo, error) {
	commandResult, err := context.Execute(request.Target, &ManagedCommand{
		Executions: []*Execution{
			{
				Command: fmt.Sprintf("kill -9 %v", request.Pid),
			},
		},
	})
	if err != nil {
		return nil, err
	}
	state := context.State()
	if !state.Has(processesKey) {
		state.Put(processesKey, common.NewMap())
	}
	var processes = state.GetMap(processesKey)
	for k, pid := range processes {
		if toolbox.AsInt(pid) == request.Pid {
			state[k] = 0
			break
		}
	}
	return commandResult, err
}

func indexProcesses(processes ...*ProcessInfo) map[int]*ProcessInfo {
	var result = make(map[int]*ProcessInfo)
	for _, process := range processes {
		result[process.Pid] = process
	}
	return result
}

func (s *processService) startProcess(context *Context, request *ProcessStartRequest) (*ProcessStartResponse, error) {
	origProcesses, err := s.checkProcess(context, &ProcessStatusRequest{
		Target:  request.Target,
		Command: request.Command,
	})

	var result = &ProcessStartResponse{}
	if err != nil {
		return nil, err
	}
	for _, process := range origProcesses {
		if strings.Join(process.Arguments, " ") == strings.Join(request.Arguments, " ") {
			_, err := s.stopProcess(context, &ProcessStopRequest{
				Pid:    process.Pid,
				Target: request.Target,
			})
			if err != nil {
				return nil, err
			}
		}
	}
	changeDirCommand := fmt.Sprintf("cd %v ", request.Directory)
	startCommand := fmt.Sprintf("nohup %v %v &", request.Command, strings.Join(request.Arguments, " "))
	 _, err = context.Execute(request.Target, &ManagedCommand{
		Options: request.Options,
		Executions: []*Execution{
			{
				Command: changeDirCommand,
			},
			{
				Command: startCommand,
			},
		},
	})
	if err != nil {
		return nil, err
	}
	time.Sleep(time.Second)
	newProcesses, err := s.checkProcess(context, &ProcessStatusRequest{
		Target:  request.Target,
		Command: request.Command,
	})
	if err != nil {
		return nil, err
	}

	result.Info = make([]*ProcessInfo, 0)
	existingProcesses := indexProcesses(origProcesses...)

	for _, candidate := range newProcesses {
		if _, has := existingProcesses[candidate.Pid]; !has {
			result.Info = append(result.Info, candidate)
			break
		}
	}
	return result, nil
}

func (s *processService) NewRequest(action string) (interface{}, error) {
	switch action {
	case "start":
		return &ProcessStartRequest{}, nil
	case "check":
		return &ProcessStatusRequest{}, nil
	case "stop":
		return &ProcessStopRequest{}, nil

	}
	return s.AbstractService.NewRequest(action)
}

func NewProcessService() Service {
	var result = &processService{
		AbstractService: NewAbstractService(ProcessServiceId),
	}
	result.AbstractService.Service = result
	return result
}
<commit_msg>extended process service<commit_after>package endly

import (
	"fmt"
	"github.com/viant/endly/common"
	"github.com/viant/toolbox"
	"strings"
	"time"
)

const ProcessServiceId = "process"
const processesKey = "pid"

type ProcessStartRequest struct {
	Name          string
	Target        *Resource
	Options       *ExecutionOptions
	SystemService bool
	Directory     string
	Command       string
	Arguments     []string
}

type ProcessStartResponse struct {
	Command string
	Info    []*ProcessInfo
}

type ProcessInfo struct {
	Name      string
	Pid       int
	Command   string
	Arguments []string
	Stdin     string
	Stdout    string
}

type ProcessStatusResponse struct {
	Processes []*ProcessInfo
	Pid       int
}

type ProcessStatusRequest struct {
	Target  *Resource
	Command string
}

type ProcessStopRequest struct {
	Target *Resource
	Pid    int
}

type processService struct {
	*AbstractService
}

func (s *processService) Run(context *Context, request interface{}) *ServiceResponse {
	var response = &ServiceResponse{Status: "ok"}
	var err error
	switch actualRequest := request.(type) {
	case *ProcessStartRequest:
		response.Response, err = s.startProcess(context, actualRequest)
		if err != nil {
			response.Error = fmt.Sprintf("Failed to start process: %v, %v", actualRequest.Name, err)
		}
	case *ProcessStopRequest:
		response.Response, err = s.stopProcess(context, actualRequest)
		if err != nil {
			response.Error = fmt.Sprintf("Failed to stop process: %v, %v", actualRequest.Pid, err)
		}
	case *ProcessStatusRequest:
		response.Response, err = s.checkProcess(context, actualRequest)
		if err != nil {
			response.Error = fmt.Sprintf("Failed to stop process: %v, %v", actualRequest, err)
		}

	default:
		response.Error = fmt.Sprintf("Unsupported request type: %T", request)

	}
	if response.Error != "" {
		response.Status = "err"
	}
	return response
}

func (s *processService) checkProcess(context *Context, request *ProcessStatusRequest) (*ProcessStatusResponse, error) {
	var response = &ProcessStatusResponse{
		Processes: make([]*ProcessInfo, 0),
	}
	commandResponse, err := context.Execute(request.Target, &ManagedCommand{
		Executions: []*Execution{
			{
				Command: "ps -ev | grep " + request.Command,
			},
		},
	})

	if err != nil {
		return nil, err
	}

	var state = context.State()
	if !state.Has(processesKey) {
		state.Put(processesKey, common.NewMap())
	}
	var processes = state.GetMap(processesKey)
	processes.Put(request.Command, 0)
	for _, line := range strings.Split(commandResponse.Stdout(), "\r\n") {
		if strings.Contains(line, "grep") {
			continue
		}
		pid, ok := ExtractColumn(line, 0)
		if !ok {
			continue
		}
		argumentsIndex := strings.Index(line, request.Command)
		var arguments []string
		if argumentsIndex != -1 {
			args := strings.Trim(line[argumentsIndex+len(request.Command)+1:], " &\t")
			arguments = strings.Split(args, " ")
		}
		info := &ProcessInfo{
			Pid:       toolbox.AsInt(pid),
			Command:   request.Command,
			Arguments: arguments,
			Stdout:    line,
		}
		response.Processes = append(response.Processes, info)
		processes.Put(request.Command, info.Pid)
	}
	if len(response.Processes) > 0 {
		response.Pid = response.Processes[0].Pid
	}
	return response, nil
}

func (s *processService) stopProcess(context *Context, request *ProcessStopRequest) (*CommandInfo, error) {
	commandResult, err := context.Execute(request.Target, &ManagedCommand{
		Executions: []*Execution{
			{
				Command: fmt.Sprintf("kill -9 %v", request.Pid),
			},
		},
	})
	if err != nil {
		return nil, err
	}
	state := context.State()
	if !state.Has(processesKey) {
		state.Put(processesKey, common.NewMap())
	}
	var processes = state.GetMap(processesKey)
	for k, pid := range processes {
		if toolbox.AsInt(pid) == request.Pid {
			state[k] = 0
			break
		}
	}
	return commandResult, err
}

func indexProcesses(processes ...*ProcessInfo) map[int]*ProcessInfo {
	var result = make(map[int]*ProcessInfo)
	for _, process := range processes {
		result[process.Pid] = process
	}
	return result
}

func (s *processService) startProcess(context *Context, request *ProcessStartRequest) (*ProcessStartResponse, error) {
	origProcesses, err := s.checkProcess(context, &ProcessStatusRequest{
		Target:  request.Target,
		Command: request.Command,
	})

	var result = &ProcessStartResponse{}
	if err != nil {
		return nil, err
	}
	for _, process := range origProcesses.Processes {
		if strings.Join(process.Arguments, " ") == strings.Join(request.Arguments, " ") {
			_, err := s.stopProcess(context, &ProcessStopRequest{
				Pid:    process.Pid,
				Target: request.Target,
			})
			if err != nil {
				return nil, err
			}
		}
	}
	changeDirCommand := fmt.Sprintf("cd %v ", request.Directory)
	startCommand := fmt.Sprintf("nohup %v %v &", request.Command, strings.Join(request.Arguments, " "))
	_, err = context.Execute(request.Target, &ManagedCommand{
		Options: request.Options,
		Executions: []*Execution{
			{
				Command: changeDirCommand,
			},
			{
				Command: startCommand,
			},
		},
	})
	if err != nil {
		return nil, err
	}
	time.Sleep(time.Second)
	newProcesses, err := s.checkProcess(context, &ProcessStatusRequest{
		Target:  request.Target,
		Command: request.Command,
	})
	if err != nil {
		return nil, err
	}

	result.Info = make([]*ProcessInfo, 0)
	existingProcesses := indexProcesses(origProcesses.Processes...)

	for _, candidate := range newProcesses.Processes {
		if _, has := existingProcesses[candidate.Pid]; !has {
			result.Info = append(result.Info, candidate)
			break
		}
	}
	return result, nil
}

func (s *processService) NewRequest(action string) (interface{}, error) {
	switch action {
	case "start":
		return &ProcessStartRequest{}, nil
	case "check":
		return &ProcessStatusRequest{}, nil
	case "stop":
		return &ProcessStopRequest{}, nil

	}
	return s.AbstractService.NewRequest(action)
}

func NewProcessService() Service {
	var result = &processService{
		AbstractService: NewAbstractService(ProcessServiceId),
	}
	result.AbstractService.Service = result
	return result
}
<|endoftext|>