<commit_before>package rain

import (
	"crypto/rand"
	"net"
	"runtime"
	"sync"

	"github.com/cenkalti/log"

	"github.com/cenkalti/rain/internal/connection"
	"github.com/cenkalti/rain/internal/logger"
	"github.com/cenkalti/rain/internal/protocol"
	"github.com/cenkalti/rain/internal/torrent"
)

// Limits
const (
	maxPeerServe      = 200
	maxPeerPerTorrent = 50
	downloadSlots     = 4
	uploadSlots       = 4
)

// http://www.bittorrent.org/beps/bep_0020.html
var peerIDPrefix = []byte("-RN" + build + "-")
var build = "0001"

func SetLogLevel(l log.Level) { logger.DefaultHandler.SetLevel(l) }

type Rain struct {
	config        *Config
	peerID        protocol.PeerID
	listener      *net.TCPListener
	transfers     map[protocol.InfoHash]*transfer // all active transfers
	transfersSKey map[[20]byte]*transfer          // for encryption
	transfersM    sync.Mutex
	log           logger.Logger
}

// New returns a pointer to new Rain BitTorrent client.
func New(c *Config) (*Rain, error) {
	peerID, err := generatePeerID()
	if err != nil {
		return nil, err
	}
	return &Rain{
		config:        c,
		peerID:        peerID,
		transfers:     make(map[protocol.InfoHash]*transfer),
		transfersSKey: make(map[[20]byte]*transfer),
		log:           logger.New("rain"),
	}, nil
}

func generatePeerID() (protocol.PeerID, error) {
	var id protocol.PeerID
	copy(id[:], peerIDPrefix)
	_, err := rand.Read(id[len(peerIDPrefix):])
	return id, err
}

// Listen peer port and accept incoming peer connections.
func (r *Rain) Listen() error {
	var err error
	addr := &net.TCPAddr{Port: int(r.config.Port)}
	r.listener, err = net.ListenTCP("tcp4", addr)
	if err != nil {
		return err
	}
	r.log.Notice("Listening peers on tcp://" + r.listener.Addr().String())
	go r.accepter()
	return nil
}

func (r *Rain) Close() error { return r.listener.Close() }

func (r *Rain) accepter() {
	limit := make(chan struct{}, maxPeerServe)
	for {
		conn, err := r.listener.Accept()
		if err != nil {
			r.log.Error(err)
			return
		}
		limit <- struct{}{}
		go func(c net.Conn) {
			defer func() {
				if err := recover(); err != nil {
					buf := make([]byte, 10000)
					r.log.Critical(err, "\n", string(buf[:runtime.Stack(buf, false)]))
				}
				c.Close()
				<-limit
			}()
			r.servePeer(c)
		}(conn)
	}
}

func (r *Rain) PeerID() protocol.PeerID { return r.peerID }
func (r *Rain) Port() uint16            { return r.config.Port }

func (r *Rain) servePeer(conn net.Conn) {
	p := newPeer(conn, incoming)

	getSKey := func(sKeyHash [20]byte) (sKey []byte) {
		r.transfersM.Lock()
		t, ok := r.transfersSKey[sKeyHash]
		r.transfersM.Unlock()
		if ok {
			sKey = t.torrent.Info.Hash[:]
		}
		return
	}

	hasInfoHash := func(ih protocol.InfoHash) bool {
		r.transfersM.Lock()
		_, ok := r.transfers[ih]
		r.transfersM.Unlock()
		return ok
	}

	encConn, _, _, ih, _, err := connection.Accept(p.conn, getSKey, r.config.Encryption.ForceIncoming, hasInfoHash, [8]byte{}, r.peerID)
	if err != nil {
		if err == connection.ErrOwnConnection {
			r.log.Debug(err)
		} else {
			r.log.Error(err)
		}
		return
	}
	p.conn = encConn
	p.log.Info("Connection accepted")

	r.transfersM.Lock()
	t, ok := r.transfers[ih]
	r.transfersM.Unlock()
	if !ok {
		r.log.Debug("Transfer is removed during incoming handshake")
		return
	}

	p.log.Debugln("servePeerConn: Handshake completed")
	p.Serve(t)
}

func (r *Rain) Add(torrentPath, where string) (*transfer, error) {
	torrent, err := torrent.New(torrentPath)
	if err != nil {
		return nil, err
	}
	r.log.Debugf("Parsed torrent file: %#v", torrent)
	return r.newTransfer(torrent, where)
}

func (r *Rain) AddMagnet(url, where string) (*transfer, error) { panic("not implemented") }

func (r *Rain) Start(t *transfer)  { go t.Run() }
func (r *Rain) Stop(t *transfer)   { panic("not implemented") }
func (r *Rain) Remove(t *transfer) { panic("not implemented") }
<commit_msg>refactor<commit_after>package rain

import (
	"crypto/rand"
	"net"
	"runtime"
	"sync"

	"github.com/cenkalti/log"

	"github.com/cenkalti/rain/internal/connection"
	"github.com/cenkalti/rain/internal/logger"
	"github.com/cenkalti/rain/internal/protocol"
	"github.com/cenkalti/rain/internal/torrent"
)

// Limits
const (
	maxPeerServe      = 200
	maxPeerPerTorrent = 50
	downloadSlots     = 4
	uploadSlots       = 4
)

// http://www.bittorrent.org/beps/bep_0020.html
var peerIDPrefix = []byte("-RN" + build + "-")
var build = "0001"

func SetLogLevel(l log.Level) { logger.DefaultHandler.SetLevel(l) }

type Rain struct {
	config        *Config
	peerID        protocol.PeerID
	listener      *net.TCPListener
	transfers     map[protocol.InfoHash]*transfer // all active transfers
	transfersSKey map[[20]byte]*transfer          // for encryption
	transfersM    sync.Mutex
	log           logger.Logger
}

// New returns a pointer to new Rain BitTorrent client.
func New(c *Config) (*Rain, error) {
	peerID, err := generatePeerID()
	if err != nil {
		return nil, err
	}
	return &Rain{
		config:        c,
		peerID:        peerID,
		transfers:     make(map[protocol.InfoHash]*transfer),
		transfersSKey: make(map[[20]byte]*transfer),
		log:           logger.New("rain"),
	}, nil
}

func generatePeerID() (protocol.PeerID, error) {
	var id protocol.PeerID
	copy(id[:], peerIDPrefix)
	_, err := rand.Read(id[len(peerIDPrefix):])
	return id, err
}

// Listen peer port and accept incoming peer connections.
func (r *Rain) Listen() error {
	var err error
	addr := &net.TCPAddr{Port: int(r.config.Port)}
	r.listener, err = net.ListenTCP("tcp4", addr)
	if err != nil {
		return err
	}
	r.log.Notice("Listening peers on tcp://" + r.listener.Addr().String())
	go r.accepter()
	return nil
}

func (r *Rain) Close() error { return r.listener.Close() }

func (r *Rain) accepter() {
	limit := make(chan struct{}, maxPeerServe)
	for {
		conn, err := r.listener.Accept()
		if err != nil {
			r.log.Error(err)
			return
		}
		limit <- struct{}{}
		go func(c net.Conn) {
			defer func() {
				if err := recover(); err != nil {
					buf := make([]byte, 10000)
					r.log.Critical(err, "\n", string(buf[:runtime.Stack(buf, false)]))
				}
				c.Close()
				<-limit
			}()
			r.servePeer(c)
		}(conn)
	}
}

func (r *Rain) PeerID() protocol.PeerID { return r.peerID }
func (r *Rain) Port() uint16            { return r.config.Port }

func (r *Rain) servePeer(conn net.Conn) {
	getSKey := func(sKeyHash [20]byte) (sKey []byte) {
		r.transfersM.Lock()
		t, ok := r.transfersSKey[sKeyHash]
		r.transfersM.Unlock()
		if ok {
			sKey = t.torrent.Info.Hash[:]
		}
		return
	}

	hasInfoHash := func(ih protocol.InfoHash) bool {
		r.transfersM.Lock()
		_, ok := r.transfers[ih]
		r.transfersM.Unlock()
		return ok
	}

	encConn, _, _, ih, _, err := connection.Accept(conn, getSKey, r.config.Encryption.ForceIncoming, hasInfoHash, [8]byte{}, r.peerID)
	if err != nil {
		if err == connection.ErrOwnConnection {
			r.log.Debug(err)
		} else {
			r.log.Error(err)
		}
		return
	}

	p := newPeer(encConn, incoming)
	p.log.Info("Connection accepted")

	r.transfersM.Lock()
	t, ok := r.transfers[ih]
	r.transfersM.Unlock()
	if !ok {
		r.log.Debug("Transfer is removed during incoming handshake")
		return
	}

	p.log.Debugln("servePeerConn: Handshake completed")
	p.Serve(t)
}

func (r *Rain) Add(torrentPath, where string) (*transfer, error) {
	torrent, err := torrent.New(torrentPath)
	if err != nil {
		return nil, err
	}
	r.log.Debugf("Parsed torrent file: %#v", torrent)
	return r.newTransfer(torrent, where)
}

func (r *Rain) AddMagnet(url, where string) (*transfer, error) { panic("not implemented") }

func (r *Rain) Start(t *transfer)  { go t.Run() }
func (r *Rain) Stop(t *transfer)   { panic("not implemented") }
func (r *Rain) Remove(t *transfer) { panic("not implemented") }
<|endoftext|>


<commit_before>// 文件数据记录类
// create by gloomy 2017-04-06 10:11:35
package gutil

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const maxFileDataRecordingBytes = 1000000 // 默认文件大小

// 文件数据记录对象
// create by gloomy 2017-04-06 10:15:00
type FileDataRecording struct {
	sync.Mutex                         // 锁
	F                         *os.File // 文件对象
	FilePre                   string   // 文件开头字符串
	Fn                        string   // 文件路径
	Bytes                     int      // 文件大小
	Seq                       int      // 第几个
	FileProgram               string   // 文件存放路径
	MaxFileDataRecordingBytes int      // 文件大小
}

// 打开文件数据记录
// create by gloomy 2017-04-06 10:17:38
// 文件存放目录地址 文件开头字符串 文件大小
// 文件数据对象
func OpenLoadFile(fileProgram, filePre string, maxSize int) *FileDataRecording {
	if maxSize == 0 {
		maxSize = maxFileDataRecordingBytes
	}
	lf := &FileDataRecording{
		FilePre:                   filePre,
		FileProgram:               fileProgram,
		MaxFileDataRecordingBytes: maxSize,
	}
	lf.Rotate()
	return lf
}

// 文件退出
// create by gloomy 2017-04-06 10:27:58
func (f *FileDataRecording) Exit() {
	f.Lock()
	f.Close()
	f.F = nil
	f.Unlock()
}

// 文件关闭
// create by gloomy 2017-04-06 10:22:14
func (f *FileDataRecording) Close() {
	if f.F != nil {
		f.F.Close()
		os.Rename(f.Fn, f.Fn[0:len(f.Fn)-4]) //去掉末尾的.tmp
	}
}

// 文件切换
// create by gloomy 2017-04-06 10:30:05
func (f *FileDataRecording) Rotate() {
	f.Lock()
	f.Seq = 0
	f.Close()
	f.CreateNewFile()
	f.Unlock()
}

// 创建新文件
// create by gloomy 2017-04-06 10:33:11
// 错误对象
func (f *FileDataRecording) CreateNewFile() (err error) {
	f.Bytes = 0
	if !strings.HasSuffix(f.FileProgram, "/") {
		f.FileProgram += "/"
	}
	f.Fn = fmt.Sprintf("%s%s-%d-%d.tmp", f.FileProgram, f.FilePre, time.Now().UnixNano(), f.Seq)
	f.Seq++
	f.F, err = os.OpenFile(f.Fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("create file %s failed: %s \n", f.Fn, err.Error())
	}
	return
}

// 写入数据
// create by gloomy 2017-04-06 11:40:55
// 需要写入的数据
// 错误对象
func (f *FileDataRecording) WriteData(dataStr string) (err error) {
	f.Lock()
	defer f.Unlock()
	if f.F == nil {
		err = f.CreateNewFile()
		if err != nil {
			return
		}
	}
	dataStrLen := len(dataStr)
	if f.Bytes+dataStrLen > f.MaxFileDataRecordingBytes {
		f.Close()
		if err = f.CreateNewFile(); err != nil {
			return
		}
	}
	f.Bytes += dataStrLen
	_, err = f.F.WriteString(dataStr)
	return
}

// 获取所有完成的文件列表
// create by gloomy 2017-04-06 13:46:51
// 文件列表
func (f *FileDataRecording) FileList() *[]string {
	var (
		fileArray []string
	)
	filepath.Walk(f.FileProgram, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if ext := filepath.Ext(path); ext == ".tmp" {
			return nil
		}
		if info.Size() == 0 {
			os.Remove(path)
			return nil
		}
		if strings.HasPrefix(path, f.FilePre) {
			fileArray = append(fileArray, path)
		}
		return nil
	})
	return &fileArray
}

// 删除过期文件
// create by gloomy 2017-04-06 22:53:17
// 几天前
func (f *FileDataRecording) RemoveOldFileList(days int) {
	var (
		timeDate   = time.Now().AddDate(0, 0, days).UnixNano()
		unixNumber int
		fileName   []string
	)
	filepath.Walk(f.FileProgram, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if ext := filepath.Ext(path); ext == ".tmp" {
			return nil
		}
		if info.Size() == 0 {
			os.Remove(path)
			return nil
		}
		if strings.HasPrefix(path, f.FilePre) {
			fileName = strings.Split(path, "-")
			if len(fileName) == 3 {
				unixNumber, err = strconv.Atoi(fileName[1])
				if err != nil {
					fmt.Printf("FileListRemoveOld fileProgram: %s path: %s err: %s \n", f.FileProgram, path, err.Error())
					return nil
				}
				if timeDate >= int64(unixNumber) {
					os.Remove(path)
				}
			}
		}
		return nil
	})
}
<commit_msg>修正获取文件列表及删除旧文件数据方法<commit_after>// 文件数据记录类
// create by gloomy 2017-04-06 10:11:35
package gutil

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const maxFileDataRecordingBytes = 1000000 // 默认文件大小

// 文件数据记录对象
// create by gloomy 2017-04-06 10:15:00
type FileDataRecording struct {
	sync.Mutex                         // 锁
	F                         *os.File // 文件对象
	FilePre                   string   // 文件开头字符串
	Fn                        string   // 文件路径
	Bytes                     int      // 文件大小
	Seq                       int      // 第几个
	FileProgram               string   // 文件存放路径
	MaxFileDataRecordingBytes int      // 文件大小
}

// 打开文件数据记录
// create by gloomy 2017-04-06 10:17:38
// 文件存放目录地址 文件开头字符串 文件大小
// 文件数据对象
func OpenLoadFile(fileProgram, filePre string, maxSize int) *FileDataRecording {
	if maxSize == 0 {
		maxSize = maxFileDataRecordingBytes
	}
	lf := &FileDataRecording{
		FilePre:                   filePre,
		FileProgram:               fileProgram,
		MaxFileDataRecordingBytes: maxSize,
	}
	lf.Rotate()
	return lf
}

// 文件退出
// create by gloomy 2017-04-06 10:27:58
func (f *FileDataRecording) Exit() {
	f.Lock()
	f.Close()
	f.F = nil
	f.Unlock()
}

// 文件关闭
// create by gloomy 2017-04-06 10:22:14
func (f *FileDataRecording) Close() {
	if f.F != nil {
		f.F.Close()
		os.Rename(f.Fn, f.Fn[0:len(f.Fn)-4]) //去掉末尾的.tmp
	}
}

// 文件切换
// create by gloomy 2017-04-06 10:30:05
func (f *FileDataRecording) Rotate() {
	f.Lock()
	f.Seq = 0
	f.Close()
	f.CreateNewFile()
	f.Unlock()
}

// 创建新文件
// create by gloomy 2017-04-06 10:33:11
// 错误对象
func (f *FileDataRecording) CreateNewFile() (err error) {
	f.Bytes = 0
	if !strings.HasSuffix(f.FileProgram, "/") {
		f.FileProgram += "/"
	}
	f.Fn = fmt.Sprintf("%s%s-%d-%d.tmp", f.FileProgram, f.FilePre, time.Now().UnixNano(), f.Seq)
	f.Seq++
	f.F, err = os.OpenFile(f.Fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("create file %s failed: %s \n", f.Fn, err.Error())
	}
	return
}

// 写入数据
// create by gloomy 2017-04-06 11:40:55
// 需要写入的数据
// 错误对象
func (f *FileDataRecording) WriteData(dataStr string) (err error) {
	f.Lock()
	defer f.Unlock()
	if f.F == nil {
		err = f.CreateNewFile()
		if err != nil {
			return
		}
	}
	dataStrLen := len(dataStr)
	if f.Bytes+dataStrLen > f.MaxFileDataRecordingBytes {
		f.Close()
		if err = f.CreateNewFile(); err != nil {
			return
		}
	}
	f.Bytes += dataStrLen
	_, err = f.F.WriteString(dataStr)
	return
}

// 获取所有完成的文件列表
// create by gloomy 2017-04-06 13:46:51
// 文件列表
func (f *FileDataRecording) FileList() *[]string {
	var (
		fileArray    []string
		ext          string
		err          error
		fileInfo     os.FileInfo
		fileNamePath string
	)
	fileNameIn, err := GetMyAllFileByDir(f.FileProgram)
	if err != nil {
		return &fileArray
	}
	for _, fileName := range *fileNameIn {
		if ext = filepath.Ext(fileName); ext == ".tmp" {
			continue
		}
		fileNamePath = fmt.Sprintf("%s%s", f.FileProgram, fileName)
		fileInfo, err = os.Stat(fileNamePath)
		if err != nil {
			continue
		}
		if fileInfo.Size() == 0 {
			os.Remove(fileNamePath)
			continue
		}
		if strings.HasPrefix(fileName, f.FilePre) {
			fileArray = append(fileArray, fileNamePath)
		}
	}
	return &fileArray
}

// 删除过期文件
// create by gloomy 2017-04-06 22:53:17
// 几天前
func (f *FileDataRecording) RemoveOldFileList(days int) {
	var (
		timeDate      = time.Now().AddDate(0, 0, days).UnixNano()
		unixNumber    int
		fileNameSplit []string
		err           error
		ext           string
		fileInfo      os.FileInfo
		fileNamePath  string
	)
	fileNameIn, err := GetMyAllFileByDir(f.FileProgram)
	if err != nil {
		return
	}
	for _, fileName := range *fileNameIn {
		if ext = filepath.Ext(fileName); ext == ".tmp" {
			continue
		}
		fileNamePath = fmt.Sprintf("%s%s", f.FileProgram, fileName)
		fileInfo, err = os.Stat(fileNamePath)
		if err != nil {
			continue
		}
		if fileInfo.Size() == 0 {
			os.Remove(fileNamePath)
			continue
		}
		if strings.HasPrefix(fileName, f.FilePre) {
			fileNameSplit = strings.Split(fileName, "-")
			if len(fileNameSplit) == 3 {
				unixNumber, err = strconv.Atoi(fileNameSplit[1])
				if err != nil {
					fmt.Printf("FileListRemoveOld fileProgram: %s path: %s err: %s \n", f.FileProgram, fileName, err.Error())
					continue
				}
				if timeDate >= int64(unixNumber) {
					os.Remove(fileNamePath)
				}
			}
		}
	}
}
<|endoftext|>


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

package xfs_test

import (
	"log"
	"reflect"
	"strings"
	"testing"

	"github.com/prometheus/procfs"
	"github.com/prometheus/procfs/xfs"
)

func TestParseStats(t *testing.T) {
	tests := []struct {
		name    string
		s       string
		fs      bool
		stats   *xfs.Stats
		invalid bool
	}{
		{
			name: "empty file OK",
		},
		{
			name:  "short or empty lines and unknown labels ignored",
			s:     "one\n\ntwo 1 2 3\n",
			stats: &xfs.Stats{},
		},
		{
			name:    "bad uint32",
			s:       "extent_alloc XXX",
			invalid: true,
		},
		{
			name:    "bad uint64",
			s:       "xpc XXX",
			invalid: true,
		},
		{
			name:    "extent_alloc bad",
			s:       "extent_alloc 1",
			invalid: true,
		},
		{
			name: "extent_alloc OK",
			s:    "extent_alloc 1 2 3 4",
			stats: &xfs.Stats{
				ExtentAllocation: xfs.ExtentAllocationStats{
					ExtentsAllocated: 1,
					BlocksAllocated:  2,
					ExtentsFreed:     3,
					BlocksFreed:      4,
				},
			},
		},
		{
			name:    "abt bad",
			s:       "abt 1",
			invalid: true,
		},
		{
			name: "abt OK",
			s:    "abt 1 2 3 4",
			stats: &xfs.Stats{
				AllocationBTree: xfs.BTreeStats{
					Lookups:         1,
					Compares:        2,
					RecordsInserted: 3,
					RecordsDeleted:  4,
				},
			},
		},
		{
			name:    "blk_map bad",
			s:       "blk_map 1",
			invalid: true,
		},
		{
			name: "blk_map OK",
			s:    "blk_map 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				BlockMapping: xfs.BlockMappingStats{
					Reads:                1,
					Writes:               2,
					Unmaps:               3,
					ExtentListInsertions: 4,
					ExtentListDeletions:  5,
					ExtentListLookups:    6,
					ExtentListCompares:   7,
				},
			},
		},
		{
			name:    "bmbt bad",
			s:       "bmbt 1",
			invalid: true,
		},
		{
			name: "bmbt OK",
			s:    "bmbt 1 2 3 4",
			stats: &xfs.Stats{
				BlockMapBTree: xfs.BTreeStats{
					Lookups:         1,
					Compares:        2,
					RecordsInserted: 3,
					RecordsDeleted:  4,
				},
			},
		},
		{
			name:    "dir bad",
			s:       "dir 1",
			invalid: true,
		},
		{
			name: "dir OK",
			s:    "dir 1 2 3 4",
			stats: &xfs.Stats{
				DirectoryOperation: xfs.DirectoryOperationStats{
					Lookups:  1,
					Creates:  2,
					Removes:  3,
					Getdents: 4,
				},
			},
		},
		{
			name:    "trans bad",
			s:       "trans 1",
			invalid: true,
		},
		{
			name: "trans OK",
			s:    "trans 1 2 3",
			stats: &xfs.Stats{
				Transaction: xfs.TransactionStats{
					Sync:  1,
					Async: 2,
					Empty: 3,
				},
			},
		},
		{
			name:    "ig bad",
			s:       "ig 1",
			invalid: true,
		},
		{
			name: "ig OK",
			s:    "ig 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				InodeOperation: xfs.InodeOperationStats{
					Attempts:        1,
					Found:           2,
					Recycle:         3,
					Missed:          4,
					Duplicate:       5,
					Reclaims:        6,
					AttributeChange: 7,
				},
			},
		},
		{
			name:    "log bad",
			s:       "log 1",
			invalid: true,
		},
		{
			name: "log OK",
			s:    "log 1 2 3 4 5",
			stats: &xfs.Stats{
				LogOperation: xfs.LogOperationStats{
					Writes:            1,
					Blocks:            2,
					NoInternalBuffers: 3,
					Force:             4,
					ForceSleep:        5,
				},
			},
		},
		{
			name:    "rw bad",
			s:       "rw 1",
			invalid: true,
		},
		{
			name: "rw OK",
			s:    "rw 1 2",
			stats: &xfs.Stats{
				ReadWrite: xfs.ReadWriteStats{
					Read:  1,
					Write: 2,
				},
			},
		},
		{
			name:    "attr bad",
			s:       "attr 1",
			invalid: true,
		},
		{
			name: "attr OK",
			s:    "attr 1 2 3 4",
			stats: &xfs.Stats{
				AttributeOperation: xfs.AttributeOperationStats{
					Get:    1,
					Set:    2,
					Remove: 3,
					List:   4,
				},
			},
		},
		{
			name:    "icluster bad",
			s:       "icluster 1",
			invalid: true,
		},
		{
			name: "icluster OK",
			s:    "icluster 1 2 3",
			stats: &xfs.Stats{
				InodeClustering: xfs.InodeClusteringStats{
					Iflush:     1,
					Flush:      2,
					FlushInode: 3,
				},
			},
		},
		{
			name:    "vnodes bad",
			s:       "vnodes 1",
			invalid: true,
		},
		{
			name: "vnodes (missing free) OK",
			s:    "vnodes 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				Vnode: xfs.VnodeStats{
					Active:   1,
					Allocate: 2,
					Get:      3,
					Hold:     4,
					Release:  5,
					Reclaim:  6,
					Remove:   7,
				},
			},
		},
		{
			name: "vnodes (with free) OK",
			s:    "vnodes 1 2 3 4 5 6 7 8",
			stats: &xfs.Stats{
				Vnode: xfs.VnodeStats{
					Active:   1,
					Allocate: 2,
					Get:      3,
					Hold:     4,
					Release:  5,
					Reclaim:  6,
					Remove:   7,
					Free:     8,
				},
			},
		},
		{
			name:    "buf bad",
			s:       "buf 1",
			invalid: true,
		},
		{
			name: "buf OK",
			s:    "buf 1 2 3 4 5 6 7 8 9",
			stats: &xfs.Stats{
				Buffer: xfs.BufferStats{
					Get:             1,
					Create:          2,
					GetLocked:       3,
					GetLockedWaited: 4,
					BusyLocked:      5,
					MissLocked:      6,
					PageRetries:     7,
					PageFound:       8,
					GetRead:         9,
				},
			},
		},
		{
			name:    "xpc bad",
			s:       "xpc 1",
			invalid: true,
		},
		{
			name: "xpc OK",
			s:    "xpc 1 2 3",
			stats: &xfs.Stats{
				ExtendedPrecision: xfs.ExtendedPrecisionStats{
					FlushBytes: 1,
					WriteBytes: 2,
					ReadBytes:  3,
				},
			},
		},
		{
			name: "fixtures OK",
			fs:   true,
			stats: &xfs.Stats{
				ExtentAllocation: xfs.ExtentAllocationStats{
					ExtentsAllocated: 92447,
					BlocksAllocated:  97589,
					ExtentsFreed:     92448,
					BlocksFreed:      93751,
				},
				AllocationBTree: xfs.BTreeStats{
					Lookups:         0,
					Compares:        0,
					RecordsInserted: 0,
					RecordsDeleted:  0,
				},
				BlockMapping: xfs.BlockMappingStats{
					Reads:                1767055,
					Writes:               188820,
					Unmaps:               184891,
					ExtentListInsertions: 92447,
					ExtentListDeletions:  92448,
					ExtentListLookups:    2140766,
					ExtentListCompares:   0,
				},
				BlockMapBTree: xfs.BTreeStats{
					Lookups:         0,
					Compares:        0,
					RecordsInserted: 0,
					RecordsDeleted:  0,
				},
				DirectoryOperation: xfs.DirectoryOperationStats{
					Lookups:  185039,
					Creates:  92447,
					Removes:  92444,
					Getdents: 136422,
				},
				Transaction: xfs.TransactionStats{
					Sync:  706,
					Async: 944304,
					Empty: 0,
				},
				InodeOperation: xfs.InodeOperationStats{
					Attempts:        185045,
					Found:           58807,
					Recycle:         0,
					Missed:          126238,
					Duplicate:       0,
					Reclaims:        33637,
					AttributeChange: 22,
				},
				LogOperation: xfs.LogOperationStats{
					Writes:            2883,
					Blocks:            113448,
					NoInternalBuffers: 9,
					Force:             17360,
					ForceSleep:        739,
				},
				ReadWrite: xfs.ReadWriteStats{
					Read:  107739,
					Write: 94045,
				},
				AttributeOperation: xfs.AttributeOperationStats{
					Get:    4,
					Set:    0,
					Remove: 0,
					List:   0,
				},
				InodeClustering: xfs.InodeClusteringStats{
					Iflush:     8677,
					Flush:      7849,
					FlushInode: 135802,
				},
				Vnode: xfs.VnodeStats{
					Active:   92601,
					Allocate: 0,
					Get:      0,
					Hold:     0,
					Release:  92444,
					Reclaim:  92444,
					Remove:   92444,
					Free:     0,
				},
				Buffer: xfs.BufferStats{
					Get:             2666287,
					Create:          7122,
					GetLocked:       2659202,
					GetLockedWaited: 3599,
					BusyLocked:      2,
					MissLocked:      7085,
					PageRetries:     0,
					PageFound:       10297,
					GetRead:         7085,
				},
				ExtendedPrecision: xfs.ExtendedPrecisionStats{
					FlushBytes: 399724544,
					WriteBytes: 92823103,
					ReadBytes:  86219234,
				},
			},
		},
	}

	for i, tt := range tests {
		t.Logf("[%02d] test %q", i, tt.name)

		var (
			stats *xfs.Stats
			err   error
		)

		if tt.s != "" {
			stats, err = xfs.ParseStats(strings.NewReader(tt.s))
		}
		if tt.fs {
			stats, err = procfs.FS("../fixtures").XFSStats()
		}

		if tt.invalid && err == nil {
			t.Error("expected an error, but none occurred")
		}
		if !tt.invalid && err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if want, have := tt.stats, stats; !reflect.DeepEqual(want, have) {
			log.Printf("stats: %#v", have)
			t.Errorf("unexpected XFS stats:\nwant:\n%v\nhave:\n%v", want, have)
		}
	}
}
<commit_msg>Remove logging from xfs_test<commit_after>// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xfs_test

import (
	"reflect"
	"strings"
	"testing"

	"github.com/prometheus/procfs"
	"github.com/prometheus/procfs/xfs"
)

func TestParseStats(t *testing.T) {
	tests := []struct {
		name    string
		s       string
		fs      bool
		stats   *xfs.Stats
		invalid bool
	}{
		{
			name: "empty file OK",
		},
		{
			name:  "short or empty lines and unknown labels ignored",
			s:     "one\n\ntwo 1 2 3\n",
			stats: &xfs.Stats{},
		},
		{
			name:    "bad uint32",
			s:       "extent_alloc XXX",
			invalid: true,
		},
		{
			name:    "bad uint64",
			s:       "xpc XXX",
			invalid: true,
		},
		{
			name:    "extent_alloc bad",
			s:       "extent_alloc 1",
			invalid: true,
		},
		{
			name: "extent_alloc OK",
			s:    "extent_alloc 1 2 3 4",
			stats: &xfs.Stats{
				ExtentAllocation: xfs.ExtentAllocationStats{
					ExtentsAllocated: 1,
					BlocksAllocated:  2,
					ExtentsFreed:     3,
					BlocksFreed:      4,
				},
			},
		},
		{
			name:    "abt bad",
			s:       "abt 1",
			invalid: true,
		},
		{
			name: "abt OK",
			s:    "abt 1 2 3 4",
			stats: &xfs.Stats{
				AllocationBTree: xfs.BTreeStats{
					Lookups:         1,
					Compares:        2,
					RecordsInserted: 3,
					RecordsDeleted:  4,
				},
			},
		},
		{
			name:    "blk_map bad",
			s:       "blk_map 1",
			invalid: true,
		},
		{
			name: "blk_map OK",
			s:    "blk_map 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				BlockMapping: xfs.BlockMappingStats{
					Reads:                1,
					Writes:               2,
					Unmaps:               3,
					ExtentListInsertions: 4,
					ExtentListDeletions:  5,
					ExtentListLookups:    6,
					ExtentListCompares:   7,
				},
			},
		},
		{
			name:    "bmbt bad",
			s:       "bmbt 1",
			invalid: true,
		},
		{
			name: "bmbt OK",
			s:    "bmbt 1 2 3 4",
			stats: &xfs.Stats{
				BlockMapBTree: xfs.BTreeStats{
					Lookups:         1,
					Compares:        2,
					RecordsInserted: 3,
					RecordsDeleted:  4,
				},
			},
		},
		{
			name:    "dir bad",
			s:       "dir 1",
			invalid: true,
		},
		{
			name: "dir OK",
			s:    "dir 1 2 3 4",
			stats: &xfs.Stats{
				DirectoryOperation: xfs.DirectoryOperationStats{
					Lookups:  1,
					Creates:  2,
					Removes:  3,
					Getdents: 4,
				},
			},
		},
		{
			name:    "trans bad",
			s:       "trans 1",
			invalid: true,
		},
		{
			name: "trans OK",
			s:    "trans 1 2 3",
			stats: &xfs.Stats{
				Transaction: xfs.TransactionStats{
					Sync:  1,
					Async: 2,
					Empty: 3,
				},
			},
		},
		{
			name:    "ig bad",
			s:       "ig 1",
			invalid: true,
		},
		{
			name: "ig OK",
			s:    "ig 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				InodeOperation: xfs.InodeOperationStats{
					Attempts:        1,
					Found:           2,
					Recycle:         3,
					Missed:          4,
					Duplicate:       5,
					Reclaims:        6,
					AttributeChange: 7,
				},
			},
		},
		{
			name:    "log bad",
			s:       "log 1",
			invalid: true,
		},
		{
			name: "log OK",
			s:    "log 1 2 3 4 5",
			stats: &xfs.Stats{
				LogOperation: xfs.LogOperationStats{
					Writes:            1,
					Blocks:            2,
					NoInternalBuffers: 3,
					Force:             4,
					ForceSleep:        5,
				},
			},
		},
		{
			name:    "rw bad",
			s:       "rw 1",
			invalid: true,
		},
		{
			name: "rw OK",
			s:    "rw 1 2",
			stats: &xfs.Stats{
				ReadWrite: xfs.ReadWriteStats{
					Read:  1,
					Write: 2,
				},
			},
		},
		{
			name:    "attr bad",
			s:       "attr 1",
			invalid: true,
		},
		{
			name: "attr OK",
			s:    "attr 1 2 3 4",
			stats: &xfs.Stats{
				AttributeOperation: xfs.AttributeOperationStats{
					Get:    1,
					Set:    2,
					Remove: 3,
					List:   4,
				},
			},
		},
		{
			name:    "icluster bad",
			s:       "icluster 1",
			invalid: true,
		},
		{
			name: "icluster OK",
			s:    "icluster 1 2 3",
			stats: &xfs.Stats{
				InodeClustering: xfs.InodeClusteringStats{
					Iflush:     1,
					Flush:      2,
					FlushInode: 3,
				},
			},
		},
		{
			name:    "vnodes bad",
			s:       "vnodes 1",
			invalid: true,
		},
		{
			name: "vnodes (missing free) OK",
			s:    "vnodes 1 2 3 4 5 6 7",
			stats: &xfs.Stats{
				Vnode: xfs.VnodeStats{
					Active:   1,
					Allocate: 2,
					Get:      3,
					Hold:     4,
					Release:  5,
					Reclaim:  6,
					Remove:   7,
				},
			},
		},
		{
			name: "vnodes (with free) OK",
			s:    "vnodes 1 2 3 4 5 6 7 8",
			stats: &xfs.Stats{
				Vnode: xfs.VnodeStats{
					Active:   1,
					Allocate: 2,
					Get:      3,
					Hold:     4,
					Release:  5,
					Reclaim:  6,
					Remove:   7,
					Free:     8,
				},
			},
		},
		{
			name:    "buf bad",
			s:       "buf 1",
			invalid: true,
		},
		{
			name: "buf OK",
			s:    "buf 1 2 3 4 5 6 7 8 9",
			stats: &xfs.Stats{
				Buffer: xfs.BufferStats{
					Get:             1,
					Create:          2,
					GetLocked:       3,
					GetLockedWaited: 4,
					BusyLocked:      5,
					MissLocked:      6,
					PageRetries:     7,
					PageFound:       8,
					GetRead:         9,
				},
			},
		},
		{
			name:    "xpc bad",
			s:       "xpc 1",
			invalid: true,
		},
		{
			name: "xpc OK",
			s:    "xpc 1 2 3",
			stats: &xfs.Stats{
				ExtendedPrecision: xfs.ExtendedPrecisionStats{
					FlushBytes: 1,
					WriteBytes: 2,
					ReadBytes:  3,
				},
			},
		},
		{
			name: "fixtures OK",
			fs:   true,
			stats: &xfs.Stats{
				ExtentAllocation: xfs.ExtentAllocationStats{
					ExtentsAllocated: 92447,
					BlocksAllocated:  97589,
					ExtentsFreed:     92448,
					BlocksFreed:      93751,
				},
				AllocationBTree: xfs.BTreeStats{
					Lookups:         0,
					Compares:        0,
					RecordsInserted: 0,
					RecordsDeleted:  0,
				},
				BlockMapping: xfs.BlockMappingStats{
					Reads:                1767055,
					Writes:               188820,
					Unmaps:               184891,
					ExtentListInsertions: 92447,
					ExtentListDeletions:  92448,
					ExtentListLookups:    2140766,
					ExtentListCompares:   0,
				},
				BlockMapBTree: xfs.BTreeStats{
					Lookups:         0,
					Compares:        0,
					RecordsInserted: 0,
					RecordsDeleted:  0,
				},
				DirectoryOperation: xfs.DirectoryOperationStats{
					Lookups:  185039,
					Creates:  92447,
					Removes:  92444,
					Getdents: 136422,
				},
				Transaction: xfs.TransactionStats{
					Sync:  706,
					Async: 944304,
					Empty: 0,
				},
				InodeOperation: xfs.InodeOperationStats{
					Attempts:        185045,
					Found:           58807,
					Recycle:         0,
					Missed:          126238,
					Duplicate:       0,
					Reclaims:        33637,
					AttributeChange: 22,
				},
				LogOperation: xfs.LogOperationStats{
					Writes:            2883,
					Blocks:            113448,
					NoInternalBuffers: 9,
					Force:             17360,
					ForceSleep:        739,
				},
				ReadWrite: xfs.ReadWriteStats{
					Read:  107739,
					Write: 94045,
				},
				AttributeOperation: xfs.AttributeOperationStats{
					Get:    4,
					Set:    0,
					Remove: 0,
					List:   0,
				},
				InodeClustering: xfs.InodeClusteringStats{
					Iflush:     8677,
					Flush:      7849,
					FlushInode: 135802,
				},
				Vnode: xfs.VnodeStats{
					Active:   92601,
					Allocate: 0,
					Get:      0,
					Hold:     0,
					Release:  92444,
					Reclaim:  92444,
					Remove:   92444,
					Free:     0,
				},
				Buffer: xfs.BufferStats{
					Get:             2666287,
					Create:          7122,
					GetLocked:       2659202,
					GetLockedWaited: 3599,
					BusyLocked:      2,
					MissLocked:      7085,
					PageRetries:     0,
					PageFound:       10297,
					GetRead:         7085,
				},
				ExtendedPrecision: xfs.ExtendedPrecisionStats{
					FlushBytes: 399724544,
					WriteBytes: 92823103,
					ReadBytes:  86219234,
				},
			},
		},
	}

	for _, tt := range tests {
		var (
			stats *xfs.Stats
			err   error
		)

		if tt.s != "" {
			stats, err = xfs.ParseStats(strings.NewReader(tt.s))
		}
		if tt.fs {
			stats, err = procfs.FS("../fixtures").XFSStats()
		}

		if tt.invalid && err == nil {
			t.Error("expected an error, but none occurred")
		}
		if !tt.invalid && err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		if want, have := tt.stats, stats; !reflect.DeepEqual(want, have) {
			t.Errorf("unexpected XFS stats:\nwant:\n%v\nhave:\n%v", want, have)
		}
	}
}
<|endoftext|>


<commit_before>package st300

import (
	"testing"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/larixsource/suntech/st"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func equalEMG(t *testing.T, expected *EmergencyReport, actual *EmergencyReport) {
	require.NotNil(t, expected)
	require.NotNil(t, actual)
	assert.Equal(t, expected.DevID, actual.DevID)
	assert.Equal(t, expected.Model, actual.Model)
	assert.Equal(t, expected.SwVer, actual.SwVer)
	assert.Equal(t, expected.Timestamp, actual.Timestamp)
	assert.Equal(t, expected.Cell, actual.Cell)
	assert.InEpsilon(t, expected.Latitude, actual.Latitude, epsilon)
	assert.InEpsilon(t, expected.Longitude, actual.Longitude, epsilon)
	assert.Equal(t, expected.Speed, actual.Speed)
	assert.Equal(t, expected.Course, actual.Course)
	assert.Equal(t, expected.Satellites, actual.Satellites)
	assert.Equal(t, expected.GPSFixed, actual.GPSFixed)
	assert.Equal(t, expected.Distance, actual.Distance)
	assert.Equal(t, expected.PowerVolt, actual.PowerVolt)
	assert.Equal(t, expected.IO, actual.IO)
	assert.Equal(t, expected.EmgID, actual.EmgID)
	assert.Equal(t, expected.DrivingHourMeter, actual.DrivingHourMeter)
	assert.Equal(t, expected.BackupVolt, actual.BackupVolt)
	assert.Equal(t, expected.RealTime, actual.RealTime)
}

func TestEMG300(t *testing.T) {
	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;1;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	expectedEMG := &EmergencyReport{
		Hdr:              EMGReport,
		DevID:            "100850000",
		Model:            st.ST300,
		SwVer:            10,
		Timestamp:        time.Date(2008, 10, 17, 7, 41, 56, 0, time.UTC),
		Cell:             "00100",
		Latitude:         37.478519,
		Longitude:        126.886819,
		Speed:            0.012,
		Course:           0,
		Satellites:       9,
		GPSFixed:         true,
		Distance:         0,
		PowerVolt:        15.3,
		IO:               "001100",
		EmgID:            st.PanicButtonEmg,
		DrivingHourMeter: 0,
		BackupVolt:       4.5,
		RealTime:         true,
	}

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, expectedEMG, msg.EMG)

	assert.False(t, p.Next())

}
<commit_msg>Adds tests for all types of EMG ID<commit_after>package st300

import (
	"testing"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/larixsource/suntech/st"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func equalEMG(t *testing.T, expected *EmergencyReport, actual *EmergencyReport) {
	require.NotNil(t, expected)
	require.NotNil(t, actual)
	assert.Equal(t, expected.DevID, actual.DevID)
	assert.Equal(t, expected.Model, actual.Model)
	assert.Equal(t, expected.SwVer, actual.SwVer)
	assert.Equal(t, expected.Timestamp, actual.Timestamp)
	assert.Equal(t, expected.Cell, actual.Cell)
	assert.InEpsilon(t, expected.Latitude, actual.Latitude, epsilon)
	assert.InEpsilon(t, expected.Longitude, actual.Longitude, epsilon)
	assert.Equal(t, expected.Speed, actual.Speed)
	assert.Equal(t, expected.Course, actual.Course)
	assert.Equal(t, expected.Satellites, actual.Satellites)
	assert.Equal(t, expected.GPSFixed, actual.GPSFixed)
	assert.Equal(t, expected.Distance, actual.Distance)
	assert.Equal(t, expected.PowerVolt, actual.PowerVolt)
	assert.Equal(t, expected.IO, actual.IO)
	assert.Equal(t, expected.EmgID, actual.EmgID)
	assert.Equal(t, expected.DrivingHourMeter, actual.DrivingHourMeter)
	assert.Equal(t, expected.BackupVolt, actual.BackupVolt)
	assert.Equal(t, expected.RealTime, actual.RealTime)
}

var testEMG = EmergencyReport{
	Hdr:              EMGReport,
	DevID:            "100850000",
	Model:            st.ST300,
	SwVer:            10,
	Timestamp:        time.Date(2008, 10, 17, 7, 41, 56, 0, time.UTC),
	Cell:             "00100",
	Latitude:         37.478519,
	Longitude:        126.886819,
	Speed:            0.012,
	Course:           0,
	Satellites:       9,
	GPSFixed:         true,
	Distance:         0,
	PowerVolt:        15.3,
	IO:               "001100",
	EmgID:            st.PanicButtonEmg,
	DrivingHourMeter: 0,
	BackupVolt:       4.5,
	RealTime:         true,
}

func TestEMG300PanicButton(t *testing.T) {
	expectedEMG := testEMG

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;1;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}

func TestEMG300ParkingLock(t *testing.T) {
	expectedEMG := testEMG
	expectedEMG.EmgID = st.ParkingLockEmg

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;2;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}

func TestEMG300RemovingMainPower(t *testing.T) {
	expectedEMG := testEMG
	expectedEMG.EmgID = st.RemovingMainPowerEmg

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;3;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}

func TestEMG300AntiThef(t *testing.T) {
	expectedEMG := testEMG
	expectedEMG.EmgID = st.AntiTheftEmg

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;5;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}

func TestEMG300AntiTheftDoor(t *testing.T) {
	expectedEMG := testEMG
	expectedEMG.EmgID = st.AntiTheftDoorEmg

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;6;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}

func TestEMG300Motion(t *testing.T) {
	expectedEMG := testEMG
	expectedEMG.EmgID = st.MotionEmg

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;7;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}

func TestEMG300AntiTheftShock(t *testing.T) {
	expectedEMG := testEMG
	expectedEMG.EmgID = st.AntiTheftShockEmg

	frame := "ST300EMG;100850000;01;010;20081017;07:41:56;00100;+37.478519;+126.886819;000.012;000.00;9;1;0;15.30;001100;8;0;4.5;1\r"
	p := ParseString(frame, ParserOpts{})
	assert.True(t, p.Next())
	assert.Nil(t, p.Error())
	msg := p.Msg()
	require.NotNil(t, msg)
	spew.Dump(msg)

	assert.EqualValues(t, st.ST300, msg.Model)
	assert.Equal(t, []byte(frame), msg.Frame)
	assert.Nil(t, msg.ParsingError)

	assert.Equal(t, msg.Type, EMGReport)
	equalEMG(t, &expectedEMG, msg.EMG)

	assert.False(t, p.Next())
}
<|endoftext|>


<commit_before>// Package function implements function-level operations.
package function

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
	"github.com/dustin/go-humanize"
	"github.com/jpillora/archive"
	"gopkg.in/validator.v2"

	"github.com/apex/apex/hooks"
	"github.com/apex/apex/utils"
)

// defaultPlugins are the default plugins which are required by Apex. Note that
// the order here is important for some plugins such as inference before the
// runtimes.
var defaultPlugins = []string{
	"inference",
	"golang",
	"python",
	"nodejs",
	"hooks",
	"env",
	"shim",
}

// InvocationType determines how an invocation request is made.
type InvocationType string

// Invocation types.
const (
	RequestResponse InvocationType = "RequestResponse"
	Event                          = "Event"
	DryRun                         = "DryRun"
)

// CurrentAlias name.
const CurrentAlias = "current"

// InvokeError records an error from an invocation.
type InvokeError struct {
	Message string   `json:"errorMessage"`
	Type    string   `json:"errorType"`
	Stack   []string `json:"stackTrace"`
	Handled bool
}

// Error message.
func (e *InvokeError) Error() string {
	return e.Message
}

// Config for a Lambda function.
type Config struct {
	Description string            `json:"description"`
	Runtime     string            `json:"runtime" validate:"nonzero"`
	Memory      int64             `json:"memory" validate:"nonzero"`
	Timeout     int64             `json:"timeout" validate:"nonzero"`
	Role        string            `json:"role" validate:"nonzero"`
	Handler     string            `json:"handler" validate:"nonzero"`
	Shim        bool              `json:"shim"`
	Environment map[string]string `json:"environment"`
	Hooks       hooks.Hooks       `json:"hooks"`
}

// Function represents a Lambda function, with configuration loaded
// from the "function.json" file on disk.
type Function struct {
	Config
	Name            string
	FunctionName    string
	Path            string
	Service         lambdaiface.LambdaAPI
	Log             log.Interface
	IgnoredPatterns []string
	Plugins         []string
}

// Open the function.json file and prime the config.
func (f *Function) Open() error {
	f.Log = f.Log.WithField("function", f.Name)

	if f.Plugins == nil {
		f.Plugins = defaultPlugins
	}

	if f.Environment == nil {
		f.Environment = make(map[string]string)
	}

	p, err := os.Open(filepath.Join(f.Path, "function.json"))
	if err == nil {
		if err := json.NewDecoder(p).Decode(&f.Config); err != nil {
			return err
		}
	}

	if err := f.hookOpen(); err != nil {
		return err
	}

	if err := validator.Validate(&f.Config); err != nil {
		return fmt.Errorf("error opening function %s: %s", f.Name, err.Error())
	}

	patterns, err := utils.ReadIgnoreFile(f.Path)
	if err != nil {
		return err
	}
	f.IgnoredPatterns = append(f.IgnoredPatterns, patterns...)

	return nil
}

// Setenv sets environment variable `name` to `value`.
func (f *Function) Setenv(name, value string) {
	f.Environment[name] = value
}

// Deploy code and then configuration.
func (f *Function) Deploy() error {
	if err := f.DeployCode(); err != nil {
		return err
	}

	return f.DeployConfig()
}

// DeployCode generates a zip and creates or updates the function.
func (f *Function) DeployCode() error {
	f.Log.Info("deploying")

	zip, err := f.BuildBytes()
	if err != nil {
		return err
	}

	if err := f.hookDeploy(); err != nil {
		return err
	}

	config, err := f.GetConfig()

	if e, ok := err.(awserr.Error); ok {
		if e.Code() == "ResourceNotFoundException" {
			return f.Create(zip)
		}
	}

	if err != nil {
		return err
	}

	remoteHash := *config.Configuration.CodeSha256
	localHash := utils.Sha256(zip)

	if localHash == remoteHash {
		f.Log.Info("unchanged")
		return nil
	}

	f.Log.WithFields(log.Fields{
		"local":  localHash,
		"remote": remoteHash,
	}).Debug("changed")

	return f.Update(zip)
}

// DeployConfig deploys changes to configuration.
func (f *Function) DeployConfig() error {
	f.Log.Info("deploying config")

	_, err := f.Service.UpdateFunctionConfiguration(&lambda.UpdateFunctionConfigurationInput{
		FunctionName: &f.FunctionName,
		MemorySize:   &f.Memory,
		Timeout:      &f.Timeout,
		Description:  &f.Description,
		Role:         &f.Role,
		Handler:      &f.Handler,
	})

	return err
}

// Delete the function including all its versions
func (f *Function) Delete() error {
	f.Log.Info("deleting")
	_, err := f.Service.DeleteFunction(&lambda.DeleteFunctionInput{
		FunctionName: &f.FunctionName,
	})
	return err
}

// GetConfig returns the function configuration.
func (f *Function) GetConfig() (*lambda.GetFunctionOutput, error) {
	f.Log.Debug("fetching config")
	return f.Service.GetFunction(&lambda.GetFunctionInput{
		FunctionName: &f.FunctionName,
	})
}

// GetConfigQualifier returns the function configuration for the given qualifier.
func (f *Function) GetConfigQualifier(s string) (*lambda.GetFunctionOutput, error) {
	f.Log.Debug("fetching config")
	return f.Service.GetFunction(&lambda.GetFunctionInput{
		FunctionName: &f.FunctionName,
		Qualifier:    &s,
	})
}

// GetConfigCurrent returns the function configuration for the current version.
func (f *Function) GetConfigCurrent() (*lambda.GetFunctionOutput, error) {
	return f.GetConfigQualifier(CurrentAlias)
}

// Update the function with the given `zip`.
func (f *Function) Update(zip []byte) error {
	f.Log.Info("updating function")

	updated, err := f.Service.UpdateFunctionCode(&lambda.UpdateFunctionCodeInput{
		FunctionName: &f.FunctionName,
		Publish:      aws.Bool(true),
		ZipFile:      zip,
	})

	if err != nil {
		return err
	}

	f.Log.Info("updating alias")

	_, err = f.Service.UpdateAlias(&lambda.UpdateAliasInput{
		FunctionName:    &f.FunctionName,
		Name:            aws.String(CurrentAlias),
		FunctionVersion: updated.Version,
	})

	if err != nil {
		return nil
	}

	f.Log.WithFields(log.Fields{
		"version": *updated.Version,
		"name":    f.FunctionName,
	}).Info("deployed")

	return nil
}

// Create the function with the given `zip`.
func (f *Function) Create(zip []byte) error {
	f.Log.Info("creating function")

	created, err := f.Service.CreateFunction(&lambda.CreateFunctionInput{
		FunctionName: &f.FunctionName,
		Description:  &f.Description,
		MemorySize:   &f.Memory,
		Timeout:      &f.Timeout,
		Runtime:      &f.Runtime,
		Handler:      &f.Handler,
		Role:         &f.Role,
		Publish:      aws.Bool(true),
		Code: &lambda.FunctionCode{
			ZipFile: zip,
		},
	})

	if err != nil {
		return err
	}

	f.Log.Info("creating alias")

	_, err = f.Service.CreateAlias(&lambda.CreateAliasInput{
		FunctionName:    &f.FunctionName,
		FunctionVersion: created.Version,
		Name:            aws.String(CurrentAlias),
	})

	if err != nil {
		return nil
	}

	f.Log.WithFields(log.Fields{
		"version": *created.Version,
		"name":    f.FunctionName,
	}).Info("deployed")

	return nil
}

// Invoke the remote Lambda function, returning the response and logs, if any.
func (f *Function) Invoke(event, context interface{}) (reply, logs io.Reader, err error) {
	eventBytes, err := json.Marshal(event)
	if err != nil {
		return nil, nil, err
	}

	contextBytes, err := json.Marshal(context)
	if err != nil {
		return nil, nil, err
	}

	res, err := f.Service.Invoke(&lambda.InvokeInput{
		ClientContext:  aws.String(base64.StdEncoding.EncodeToString(contextBytes)),
		FunctionName:   &f.FunctionName,
		InvocationType: aws.String(string(RequestResponse)),
		LogType:        aws.String("Tail"),
		Qualifier:      aws.String(CurrentAlias),
		Payload:        eventBytes,
	})

	if err != nil {
		return nil, nil, err
	}

	logs = base64.NewDecoder(base64.StdEncoding, strings.NewReader(*res.LogResult))

	if res.FunctionError != nil {
		e := &InvokeError{
			Handled: *res.FunctionError == "Handled",
		}

		if err := json.Unmarshal(res.Payload, e); err != nil {
			return nil, logs, err
		}

		return nil, logs, e
	}

	reply = bytes.NewReader(res.Payload)
	return reply, logs, nil
}

// Rollback the function to the previous.
func (f *Function) Rollback() error {
	f.Log.Info("rolling back")

	alias, err := f.Service.GetAlias(&lambda.GetAliasInput{
		FunctionName: &f.FunctionName,
		Name:         aws.String(CurrentAlias),
	})

	if err != nil {
		return err
	}

	f.Log.Infof("current version: %s", *alias.FunctionVersion)

	list, err := f.Service.ListVersionsByFunction(&lambda.ListVersionsByFunctionInput{
		FunctionName: &f.FunctionName,
	})

	if err != nil {
		return err
	}

	versions := list.Versions[1:] // remove $LATEST
	if len(versions) < 2 {
		return errors.New("Can't rollback. Only one version deployed.")
	}

	latest := *versions[len(versions)-1].Version
	prev := *versions[len(versions)-2].Version
	rollback := latest

	if *alias.FunctionVersion == latest {
		rollback = prev
	}

	f.Log.Infof("rollback to version: %s", rollback)

	_, err = f.Service.UpdateAlias(&lambda.UpdateAliasInput{
		FunctionName:    &f.FunctionName,
		Name:            aws.String(CurrentAlias),
		FunctionVersion: &rollback,
	})

	return err
}

// RollbackVersion the function to the specified version.
func (f *Function) RollbackVersion(version string) error {
	f.Log.Info("rolling back")

	alias, err := f.Service.GetAlias(&lambda.GetAliasInput{
		FunctionName: &f.FunctionName,
		Name:         aws.String(CurrentAlias),
	})

	if err != nil {
		return err
	}

	f.Log.Infof("current version: %s", *alias.FunctionVersion)

	if version == *alias.FunctionVersion {
		return errors.New("Specified version currently deployed.")
	}

	_, err = f.Service.UpdateAlias(&lambda.UpdateAliasInput{
		FunctionName:    &f.FunctionName,
		Name:            aws.String(CurrentAlias),
		FunctionVersion: &version,
	})

	return err
}

// BuildBytes returns the generated zip as bytes.
func (f *Function) BuildBytes() ([]byte, error) {
	r, err := f.Build()
	if err != nil {
		return nil, err
	}

	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}

	f.Log.Infof("created build (%s)", humanize.Bytes(uint64(len(b))))
	return b, nil
}

// Build returns the zipped contents of the function.
func (f *Function) Build() (io.Reader, error) {
	f.Log.Debugf("creating build")

	buf := new(bytes.Buffer)
	zip := archive.NewZipWriter(buf)

	if err := f.hookBuild(zip); err != nil {
		return nil, err
	}

	files, err := utils.LoadFiles(f.Path, f.IgnoredPatterns)
	if err != nil {
		return nil, err
	}

	for _, path := range files {
		f.Log.WithField("file", path).Debug("add file to zip")

		file, err := os.Open(filepath.Join(f.Path, path))
		if err != nil {
			return nil, err
		}

		if err := zip.AddFile(path, file); err != nil {
			return nil, err
		}

		if err := file.Close(); err != nil {
			return nil, err
		}
	}

	if err := zip.Close(); err != nil {
		return nil, err
	}

	return buf, nil
}

// Clean invokes the CleanHook, useful for removing build artifacts and so on.
func (f *Function) Clean() error {
	return f.hookClean()
}

// GroupName returns the CloudWatchLogs group name.
func (f *Function) GroupName() string {
	return fmt.Sprintf("/aws/lambda/%s", f.FunctionName)
}

// hookOpen calls Openers.
func (f *Function) hookOpen() error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Opener); ok {
			if err := p.Open(f); err != nil {
				return err
			}
		}
	}
	return nil
}

// hookBuild calls Builders.
func (f *Function) hookBuild(zip *archive.Archive) error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Builder); ok {
			if err := p.Build(f, zip); err != nil {
				return err
			}
		}
	}
	return nil
}

// hookClean calls Cleaners.
func (f *Function) hookClean() error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Cleaner); ok {
			if err := p.Clean(f); err != nil {
				return err
			}
		}
	}
	return nil
}

// hookDeploy calls Deployers.
func (f *Function) hookDeploy() error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Deployer); ok {
			if err := p.Deploy(f); err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>fix updating configuration. Closes #206<commit_after>// Package function implements function-level operations.
package function

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
	"github.com/dustin/go-humanize"
	"github.com/jpillora/archive"
	"gopkg.in/validator.v2"

	"github.com/apex/apex/hooks"
	"github.com/apex/apex/utils"
)

// defaultPlugins are the default plugins which are required by Apex. Note that
// the order here is important for some plugins such as inference before the
// runtimes.
var defaultPlugins = []string{
	"inference",
	"golang",
	"python",
	"nodejs",
	"hooks",
	"env",
	"shim",
}

// InvocationType determines how an invocation request is made.
type InvocationType string

// Invocation types.
const (
	RequestResponse InvocationType = "RequestResponse"
	Event                          = "Event"
	DryRun                         = "DryRun"
)

// CurrentAlias name.
const CurrentAlias = "current"

// InvokeError records an error from an invocation.
type InvokeError struct {
	Message string   `json:"errorMessage"`
	Type    string   `json:"errorType"`
	Stack   []string `json:"stackTrace"`
	Handled bool
}

// Error message.
func (e *InvokeError) Error() string {
	return e.Message
}

// Config for a Lambda function.
type Config struct {
	Description string            `json:"description"`
	Runtime     string            `json:"runtime" validate:"nonzero"`
	Memory      int64             `json:"memory" validate:"nonzero"`
	Timeout     int64             `json:"timeout" validate:"nonzero"`
	Role        string            `json:"role" validate:"nonzero"`
	Handler     string            `json:"handler" validate:"nonzero"`
	Shim        bool              `json:"shim"`
	Environment map[string]string `json:"environment"`
	Hooks       hooks.Hooks       `json:"hooks"`
}

// Function represents a Lambda function, with configuration loaded
// from the "function.json" file on disk.
type Function struct {
	Config
	Name            string
	FunctionName    string
	Path            string
	Service         lambdaiface.LambdaAPI
	Log             log.Interface
	IgnoredPatterns []string
	Plugins         []string
}

// Open the function.json file and prime the config.
func (f *Function) Open() error {
	f.Log = f.Log.WithField("function", f.Name)

	if f.Plugins == nil {
		f.Plugins = defaultPlugins
	}

	if f.Environment == nil {
		f.Environment = make(map[string]string)
	}

	p, err := os.Open(filepath.Join(f.Path, "function.json"))
	if err == nil {
		if err := json.NewDecoder(p).Decode(&f.Config); err != nil {
			return err
		}
	}

	if err := f.hookOpen(); err != nil {
		return err
	}

	if err := validator.Validate(&f.Config); err != nil {
		return fmt.Errorf("error opening function %s: %s", f.Name, err.Error())
	}

	patterns, err := utils.ReadIgnoreFile(f.Path)
	if err != nil {
		return err
	}
	f.IgnoredPatterns = append(f.IgnoredPatterns, patterns...)

	return nil
}

// Setenv sets environment variable `name` to `value`.
func (f *Function) Setenv(name, value string) {
	f.Environment[name] = value
}

// Deploy generates a zip and creates or deploy the function.
// If the configuration hasn't been changed it will deploy only code,
// otherwise it will deploy both configuration and code.
func (f *Function) Deploy() error {
	f.Log.Info("deploying")

	zip, err := f.BuildBytes()
	if err != nil {
		return err
	}

	if err := f.hookDeploy(); err != nil {
		return err
	}

	config, err := f.GetConfig()
	if e, ok := err.(awserr.Error); ok {
		if e.Code() == "ResourceNotFoundException" {
			return f.Create(zip)
		}
	}
	if err != nil {
		return err
	}

	if f.configChanged(config) {
		f.Log.Info("config changed")
		return f.DeployConfigAndCode(zip)
	}

	f.Log.Info("config unchanged")
	return f.DeployCode(zip, config)
}

// DeployCode deploys function code when changed.
func (f *Function) DeployCode(zip []byte, config *lambda.GetFunctionOutput) error {
	remoteHash := *config.Configuration.CodeSha256
	localHash := utils.Sha256(zip)

	if localHash == remoteHash {
		f.Log.Info("code unchanged")
		return nil
	}

	f.Log.WithFields(log.Fields{
		"local":  localHash,
		"remote": remoteHash,
	}).Debug("code changed")

	return f.Update(zip)
}

// DeployConfigAndCode updates config and updates function code.
func (f *Function) DeployConfigAndCode(zip []byte) error {
	f.Log.Info("updating config")

	_, err := f.Service.UpdateFunctionConfiguration(&lambda.UpdateFunctionConfigurationInput{
		FunctionName: &f.FunctionName,
		MemorySize:   &f.Memory,
		Timeout:      &f.Timeout,
		Description:  &f.Description,
		Role:         &f.Role,
		Handler:      &f.Handler,
	})
	if err != nil {
		return err
	}

	return f.Update(zip)
}

// Delete the function including all its versions
func (f *Function) Delete() error {
	f.Log.Info("deleting")
	_, err := f.Service.DeleteFunction(&lambda.DeleteFunctionInput{
		FunctionName: &f.FunctionName,
	})
	return err
}

// GetConfig returns the function configuration.
func (f *Function) GetConfig() (*lambda.GetFunctionOutput, error) {
	f.Log.Debug("fetching config")
	return f.Service.GetFunction(&lambda.GetFunctionInput{
		FunctionName: &f.FunctionName,
	})
}

// GetConfigQualifier returns the function configuration for the given qualifier.
func (f *Function) GetConfigQualifier(s string) (*lambda.GetFunctionOutput, error) {
	f.Log.Debug("fetching config")
	return f.Service.GetFunction(&lambda.GetFunctionInput{
		FunctionName: &f.FunctionName,
		Qualifier:    &s,
	})
}

// GetConfigCurrent returns the function configuration for the current version.
func (f *Function) GetConfigCurrent() (*lambda.GetFunctionOutput, error) {
	return f.GetConfigQualifier(CurrentAlias)
}

// Update the function with the given `zip`.
func (f *Function) Update(zip []byte) error {
	f.Log.Info("updating function")

	updated, err := f.Service.UpdateFunctionCode(&lambda.UpdateFunctionCodeInput{
		FunctionName: &f.FunctionName,
		Publish:      aws.Bool(true),
		ZipFile:      zip,
	})

	if err != nil {
		return err
	}

	f.Log.Info("updating alias")

	_, err = f.Service.UpdateAlias(&lambda.UpdateAliasInput{
		FunctionName:    &f.FunctionName,
		Name:            aws.String(CurrentAlias),
		FunctionVersion: updated.Version,
	})

	if err != nil {
		return nil
	}

	f.Log.WithFields(log.Fields{
		"version": *updated.Version,
		"name":    f.FunctionName,
	}).Info("function updated")

	return nil
}

// Create the function with the given `zip`.
func (f *Function) Create(zip []byte) error {
	f.Log.Info("creating function")

	created, err := f.Service.CreateFunction(&lambda.CreateFunctionInput{
		FunctionName: &f.FunctionName,
		Description:  &f.Description,
		MemorySize:   &f.Memory,
		Timeout:      &f.Timeout,
		Runtime:      &f.Runtime,
		Handler:      &f.Handler,
		Role:         &f.Role,
		Publish:      aws.Bool(true),
		Code: &lambda.FunctionCode{
			ZipFile: zip,
		},
	})

	if err != nil {
		return err
	}

	f.Log.Info("creating alias")

	_, err = f.Service.CreateAlias(&lambda.CreateAliasInput{
		FunctionName:    &f.FunctionName,
		FunctionVersion: created.Version,
		Name:            aws.String(CurrentAlias),
	})

	if err != nil {
		return nil
	}

	f.Log.WithFields(log.Fields{
		"version": *created.Version,
		"name":    f.FunctionName,
	}).Info("function created")

	return nil
}

// Invoke the remote Lambda function, returning the response and logs, if any.
func (f *Function) Invoke(event, context interface{}) (reply, logs io.Reader, err error) {
	eventBytes, err := json.Marshal(event)
	if err != nil {
		return nil, nil, err
	}

	contextBytes, err := json.Marshal(context)
	if err != nil {
		return nil, nil, err
	}

	res, err := f.Service.Invoke(&lambda.InvokeInput{
		ClientContext:  aws.String(base64.StdEncoding.EncodeToString(contextBytes)),
		FunctionName:   &f.FunctionName,
		InvocationType: aws.String(string(RequestResponse)),
		LogType:        aws.String("Tail"),
		Qualifier:      aws.String(CurrentAlias),
		Payload:        eventBytes,
	})

	if err != nil {
		return nil, nil, err
	}

	logs = base64.NewDecoder(base64.StdEncoding, strings.NewReader(*res.LogResult))

	if res.FunctionError != nil {
		e := &InvokeError{
			Handled: *res.FunctionError == "Handled",
		}

		if err := json.Unmarshal(res.Payload, e); err != nil {
			return nil, logs, err
		}

		return nil, logs, e
	}

	reply = bytes.NewReader(res.Payload)
	return reply, logs, nil
}

// Rollback the function to the previous.
func (f *Function) Rollback() error {
	f.Log.Info("rolling back")

	alias, err := f.Service.GetAlias(&lambda.GetAliasInput{
		FunctionName: &f.FunctionName,
		Name:         aws.String(CurrentAlias),
	})

	if err != nil {
		return err
	}

	f.Log.Infof("current version: %s", *alias.FunctionVersion)

	list, err := f.Service.ListVersionsByFunction(&lambda.ListVersionsByFunctionInput{
		FunctionName: &f.FunctionName,
	})

	if err != nil {
		return err
	}

	versions := list.Versions[1:] // remove $LATEST
	if len(versions) < 2 {
		return errors.New("Can't rollback. Only one version deployed.")
	}

	latest := *versions[len(versions)-1].Version
	prev := *versions[len(versions)-2].Version
	rollback := latest

	if *alias.FunctionVersion == latest {
		rollback = prev
	}

	f.Log.Infof("rollback to version: %s", rollback)

	_, err = f.Service.UpdateAlias(&lambda.UpdateAliasInput{
		FunctionName:    &f.FunctionName,
		Name:            aws.String(CurrentAlias),
		FunctionVersion: &rollback,
	})

	return err
}

// RollbackVersion the function to the specified version.
func (f *Function) RollbackVersion(version string) error {
	f.Log.Info("rolling back")

	alias, err := f.Service.GetAlias(&lambda.GetAliasInput{
		FunctionName: &f.FunctionName,
		Name:         aws.String(CurrentAlias),
	})

	if err != nil {
		return err
	}

	f.Log.Infof("current version: %s", *alias.FunctionVersion)

	if version == *alias.FunctionVersion {
		return errors.New("Specified version currently deployed.")
	}

	_, err = f.Service.UpdateAlias(&lambda.UpdateAliasInput{
		FunctionName:    &f.FunctionName,
		Name:            aws.String(CurrentAlias),
		FunctionVersion: &version,
	})

	return err
}

// BuildBytes returns the generated zip as bytes.
func (f *Function) BuildBytes() ([]byte, error) {
	r, err := f.Build()
	if err != nil {
		return nil, err
	}

	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}

	f.Log.Infof("created build (%s)", humanize.Bytes(uint64(len(b))))
	return b, nil
}

// Build returns the zipped contents of the function.
func (f *Function) Build() (io.Reader, error) {
	f.Log.Debugf("creating build")

	buf := new(bytes.Buffer)
	zip := archive.NewZipWriter(buf)

	if err := f.hookBuild(zip); err != nil {
		return nil, err
	}

	files, err := utils.LoadFiles(f.Path, f.IgnoredPatterns)
	if err != nil {
		return nil, err
	}

	for _, path := range files {
		f.Log.WithField("file", path).Debug("add file to zip")

		file, err := os.Open(filepath.Join(f.Path, path))
		if err != nil {
			return nil, err
		}

		if err := zip.AddFile(path, file); err != nil {
			return nil, err
		}

		if err := file.Close(); err != nil {
			return nil, err
		}
	}

	if err := zip.Close(); err != nil {
		return nil, err
	}

	return buf, nil
}

// Clean invokes the CleanHook, useful for removing build artifacts and so on.
func (f *Function) Clean() error {
	return f.hookClean()
}

// GroupName returns the CloudWatchLogs group name.
func (f *Function) GroupName() string {
	return fmt.Sprintf("/aws/lambda/%s", f.FunctionName)
}

// configChanged checks if function configuration differs from configuration stored in AWS Lambda
func (f *Function) configChanged(config *lambda.GetFunctionOutput) bool {
	if f.Description != *config.Configuration.Description {
		return true
	}

	if f.Memory != *config.Configuration.MemorySize {
		return true
	}

	if f.Timeout != *config.Configuration.Timeout {
		return true
	}

	if f.Role != *config.Configuration.Role {
		return true
	}

	if f.Handler != *config.Configuration.Handler {
		return true
	}

	return false
}

// hookOpen calls Openers.
func (f *Function) hookOpen() error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Opener); ok {
			if err := p.Open(f); err != nil {
				return err
			}
		}
	}
	return nil
}

// hookBuild calls Builders.
func (f *Function) hookBuild(zip *archive.Archive) error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Builder); ok {
			if err := p.Build(f, zip); err != nil {
				return err
			}
		}
	}
	return nil
}

// hookClean calls Cleaners.
func (f *Function) hookClean() error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Cleaner); ok {
			if err := p.Clean(f); err != nil {
				return err
			}
		}
	}
	return nil
}

// hookDeploy calls Deployers.
func (f *Function) hookDeploy() error {
	for _, name := range f.Plugins {
		if p, ok := plugins[name].(Deployer); ok {
			if err := p.Deploy(f); err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before><commit_msg>Help fix for --exclude-patterns - only "jfrog rt u" supports regexps.<commit_after><|endoftext|>


<commit_before>package main

import (
  "log"
  "github.com/gorilla/mux"
  "net/http"
  "net/http/httputil"
  "net/url"
  "github.com/fhs/gompd/mpd"
  "github.com/ascherkus/go-id3/src/id3"
  "os"
  "fmt"
  "encoding/json"
  "strconv"
  "time"
)

// TODO: consider if global is really the best idea, or if we should
//       make some classes, or something...
var mpd_conn *mpd.Client

func main() {
  mpd_conn = mpdConnect("localhost:6600")
  defer mpd_conn.Close()

  //log.Println(mpd_conn)
  if mpd_conn == nil {
    log.Fatal("MPD Connection is nil!")
  }

  // create a new mux router for our server.
  r := mux.NewRouter()

  // requests to `/stream` are proxied to the MPD httpd.
  r.HandleFunc("/stream",
    httputil.NewSingleHostReverseProxy(
      &url.URL{
        Scheme:"http",
        Host: "localhost:8000",
        Path: "/",
      }).ServeHTTP)

  r.HandleFunc("/songs", listSongs)
  r.HandleFunc("/current", getCurrentSong)
  r.HandleFunc("/upcoming", getUpcomingSongs)

  // This MUST go last! It takes precidence over any after it, meaning
  // the server will try to serve a file, which most likely doesn't exist,
  // and will 404.
  //
  // serve up the frontend files.
  r.PathPrefix("/").Handler(http.FileServer(http.Dir("../frontend/turbo_wookie/web")))

  // sit, waiting, like a hunter, spying on its prey.
  log.Println("Starting server on port 9000")
  http.ListenAndServe(":9000", r)
}

/********************
  Handler Functions
 ********************/

// return all songs known to MPD to the client.
func listSongs(w http.ResponseWriter, r *http.Request) {
  // get all files from MPD
  mpdfiles, err := mpd_conn.GetFiles()
  if err != nil {
    log.Println("Couldn't get a list of files...")
    log.Fatal(err)
  }

  // create a slice of id3.File s
  files := make([]*id3.File, 0)

  for _, song := range mpdfiles {
    // grab the file on the filesystem
    file, err := os.Open("mpd/music/" + song)
    if err != nil {
      log.Println("Couldn't open file: " + song)
      log.Fatal(err)
    }

    // add the current file to our slice
    id3_file := id3.Read(file)
    files = append(files, id3_file)
  }

  // send the json to the client.
  fmt.Fprintf(w, jsoniffy(files))
}

// Return a JSON representation of the currently playing song.
func getCurrentSong(w http.ResponseWriter, r *http.Request) {
  currentSong, err := mpd_conn.CurrentSong()
  if err != nil {
    log.Println("Couldn't get current song info")
    log.Fatal(err)
  }

  fmt.Fprintf(w, jsoniffy(currentSong))
}

func getUpcomingSongs(w http.ResponseWriter, r *http.Request) {
  currentSong, err := mpd_conn.CurrentSong()
  if err != nil {

    count := 0;
    for err != nil && count < 10 {
      time.Sleep(10)

      currentSong, err = mpd_conn.CurrentSong()
      count ++
    }

    if err != nil {
      log.Println("Couldn't get current song info for upcoming list")
      log.Fatal(err)
    }
  }

  pos, err := strconv.Atoi(currentSong["Pos"])
  if err != nil {
    log.Fatal(err)
  }

  playlist, err := mpd_conn.PlaylistInfo(-1, -1)
  if err != nil {
    log.Fatal(err)
  }

  upcoming := playlist[pos:]

  fmt.Fprintf(w, jsoniffy(upcoming))
}

/*******************
  Helper Functions
 *******************/

// Connect to MPD's control channel, and set the global mpd_conn to it.
func mpdConnect(url string) *mpd.Client {
  conn, err := mpd.Dial("tcp", url)

  // if we can't connect to MPD everything's fucked, nothing's going to work
  // kill all humans, and die, respectfully, after explaining what the issue
  // is.
  if err != nil {
    log.Println("\n\nServer quiting because it can't connect to MPD");
    log.Println(err)

    return nil
  }

  return conn
}

// turn anything into JSON.
func jsoniffy(v interface {}) string {
  obj, err := json.MarshalIndent(v, "", "  ")
  if err != nil {
    log.Print("Couldn't turn something into JSON: ", v)
    log.Fatal(err)
  }

  return string(obj)
}<commit_msg>so maybe this time?<commit_after>package main

import (
  "log"
  "github.com/gorilla/mux"
  "net/http"
  "net/http/httputil"
  "net/url"
  "github.com/fhs/gompd/mpd"
  "github.com/ascherkus/go-id3/src/id3"
  "os"
  "fmt"
  "encoding/json"
  "strconv"
  "time"
)

// TODO: consider if global is really the best idea, or if we should
//       make some classes, or something...
var mpd_conn *mpd.Client

func main() {
  mpd_conn = mpdConnect("localhost:6600")
  defer mpd_conn.Close()

  //log.Println(mpd_conn)
  if mpd_conn == nil {
    log.Fatal("MPD Connection is nil!")
  }

  // create a new mux router for our server.
  r := mux.NewRouter()

  // requests to `/stream` are proxied to the MPD httpd.
  r.HandleFunc("/stream",
    httputil.NewSingleHostReverseProxy(
      &url.URL{
        Scheme:"http",
        Host: "localhost:8000",
        Path: "/",
      }).ServeHTTP)

  r.HandleFunc("/songs", listSongs)
  r.HandleFunc("/current", getCurrentSong)
  r.HandleFunc("/upcoming", getUpcomingSongs)

  // This MUST go last! It takes precidence over any after it, meaning
  // the server will try to serve a file, which most likely doesn't exist,
  // and will 404.
  //
  // serve up the frontend files.
  r.PathPrefix("/").Handler(http.FileServer(http.Dir("../frontend/turbo_wookie/web")))

  // sit, waiting, like a hunter, spying on its prey.
  log.Println("Starting server on port 9000")
  http.ListenAndServe(":9000", r)
}

/********************
  Handler Functions
 ********************/

// return all songs known to MPD to the client.
func listSongs(w http.ResponseWriter, r *http.Request) {
  // get all files from MPD
  mpdfiles, err := mpd_conn.GetFiles()
  if err != nil {
    log.Println("Couldn't get a list of files...")
    log.Fatal(err)
  }

  // create a slice of id3.File s
  files := make([]*id3.File, 0)

  for _, song := range mpdfiles {
    // grab the file on the filesystem
    file, err := os.Open("mpd/music/" + song)
    if err != nil {
      log.Println("Couldn't open file: " + song)
      log.Fatal(err)
    }

    // add the current file to our slice
    id3_file := id3.Read(file)
    files = append(files, id3_file)
  }

  // send the json to the client.
  fmt.Fprintf(w, jsoniffy(files))
}

// Return a JSON representation of the currently playing song.
func getCurrentSong(w http.ResponseWriter, r *http.Request) {
  currentSong, err := mpd_conn.CurrentSong()
  if err != nil {

    count := 0;
    for err != nil && count < 10 {
      time.Sleep(10)

      currentSong, err = mpd_conn.CurrentSong()
      count ++
    }

    if err != nil {
      log.Println("Couldn't get current song info for upcoming list")
      log.Fatal(err)
    }
  }

  fmt.Fprintf(w, jsoniffy(currentSong))
}

func getUpcomingSongs(w http.ResponseWriter, r *http.Request) {
  currentSong, err := mpd_conn.CurrentSong()
  if err != nil {

    count := 0;
    for err != nil && count < 10 {
      time.Sleep(10)

      currentSong, err = mpd_conn.CurrentSong()
      count ++
    }

    if err != nil {
      log.Println("Couldn't get current song info for upcoming list")
      log.Fatal(err)
    }
  }

  pos, err := strconv.Atoi(currentSong["Pos"])
  if err != nil {
    log.Fatal(err)
  }

  playlist, err := mpd_conn.PlaylistInfo(-1, -1)
  if err != nil {
    log.Fatal(err)
  }

  upcoming := playlist[pos:]

  fmt.Fprintf(w, jsoniffy(upcoming))
}

/*******************
  Helper Functions
 *******************/

// Connect to MPD's control channel, and set the global mpd_conn to it.
func mpdConnect(url string) *mpd.Client {
  conn, err := mpd.Dial("tcp", url)

  // if we can't connect to MPD everything's fucked, nothing's going to work
  // kill all humans, and die, respectfully, after explaining what the issue
  // is.
  if err != nil {
    log.Println("\n\nServer quiting because it can't connect to MPD");
    log.Println(err)

    return nil
  }

  return conn
}

// turn anything into JSON.
func jsoniffy(v interface {}) string {
  obj, err := json.MarshalIndent(v, "", "  ")
  if err != nil {
    log.Print("Couldn't turn something into JSON: ", v)
    log.Fatal(err)
  }

  return string(obj)
}

<|endoftext|>


<commit_before>package ingester

import (
	"fmt"
	"os"

	nsq "github.com/bitly/go-nsq"
	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"

	"github.com/mattheath/phosphor/domain"
	traceproto "github.com/mattheath/phosphor/proto"
	"github.com/mattheath/phosphor/store"
)

var (
	topic   = "trace"
	channel = "phosphor-server"

	maxInFlight = 200
)

func Run(nsqLookupdHTTPAddrs []string, store *store.MemoryStore) {

	cfg := nsq.NewConfig()
	cfg.UserAgent = fmt.Sprintf("phosphor go-nsq/%s", nsq.VERSION)
	cfg.MaxInFlight = maxInFlight

	consumer, err := nsq.NewConsumer(topic, channel, cfg)
	if err != nil {
		log.Critical(err)
		os.Exit(1)
	}

	consumer.AddHandler(&IngestionHandler{})

	err = consumer.ConnectToNSQLookupds(nsqLookupdHTTPAddrs)
	if err != nil {
		log.Critical(err)
		os.Exit(1)
	}

	// Block until exit
	<-consumer.StopChan
}

// IngestionHandler exists to match the NSQ handler interface
type IngestionHandler struct{}

// HandleMessage delivered by NSQ
func (ih *IngestionHandler) HandleMessage(message *nsq.Message) error {

	p := &traceproto.TraceFrame{}
	err := proto.Unmarshal(message.Body, p)
	if err != nil {
		// returning an error to NSQ will requeue this
		// failure to unmarshal is permanent
		return nil
	}

	t := domain.FrameFromProto(p)

	log.Debugf("Received trace: %+v", t)

	return nil
}
<commit_msg>Update ingester to take store interface<commit_after>package ingester

import (
	"fmt"
	"os"

	nsq "github.com/bitly/go-nsq"
	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"

	"github.com/mattheath/phosphor/domain"
	traceproto "github.com/mattheath/phosphor/proto"
	"github.com/mattheath/phosphor/store"
)

var (
	topic   = "trace"
	channel = "phosphor-server"

	maxInFlight = 200
)

func Run(nsqLookupdHTTPAddrs []string, st store.Store) {

	cfg := nsq.NewConfig()
	cfg.UserAgent = fmt.Sprintf("phosphor go-nsq/%s", nsq.VERSION)
	cfg.MaxInFlight = maxInFlight

	consumer, err := nsq.NewConsumer(topic, channel, cfg)
	if err != nil {
		log.Critical(err)
		os.Exit(1)
	}

	consumer.AddHandler(&IngestionHandler{})

	err = consumer.ConnectToNSQLookupds(nsqLookupdHTTPAddrs)
	if err != nil {
		log.Critical(err)
		os.Exit(1)
	}

	// Block until exit
	<-consumer.StopChan
}

// IngestionHandler exists to match the NSQ handler interface
type IngestionHandler struct{}

// HandleMessage delivered by NSQ
func (ih *IngestionHandler) HandleMessage(message *nsq.Message) error {

	p := &traceproto.TraceFrame{}
	err := proto.Unmarshal(message.Body, p)
	if err != nil {
		// returning an error to NSQ will requeue this
		// failure to unmarshal is permanent
		return nil
	}

	t := domain.FrameFromProto(p)

	log.Debugf("Received trace: %+v", t)

	return nil
}
<|endoftext|>


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

import (
	"encoding/json"
	"fmt"
	"github.com/dotcloud/docker/engine"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
)

// The Cloud interface provides the contract that cloud providers should implement to enable
// running Docker containers in their cloud.
// TODO(bburns): Restructure this into Cloud, Instance and Tunnel interfaces
type Cloud interface {
	// GetPublicIPAddress returns the stringified address (e.g "1.2.3.4") of the runtime
	GetPublicIPAddress(name string, zone string) (string, error)

	// CreateInstance creates a virtual machine instance given a name and a zone.  Returns the
	// IP address of the instance.  Waits until Docker is up and functioning on the machine
	// before returning.
	CreateInstance(name string, zone string) (string, error)

	// DeleteInstance deletes a virtual machine instance, given the instance name and zone.
	DeleteInstance(name string, zone string) error

	// Open a secure tunnel (generally SSH) between the local host and a remote host.
	OpenSecureTunnel(name string, zone string, localPort int, remotePort int) (*os.Process, error)
}

func CloudBackend() engine.Installer {
	return &cloud{}
}

type cloud struct {
}

type Container struct {
	Id    string
	Image string
	Tty   bool
}

// returns true, if the connection was successful, false otherwise
type Tunnel struct {
	url.URL
}

func (t Tunnel) isActive() bool {
	_, err := http.Get(t.String())
	return err == nil
}

func (s *cloud) Install(eng *engine.Engine) error {
	eng.Register("cloud", func(job *engine.Job) engine.Status {
		if len(job.Args) < 3 {
			return job.Errorf("usage: %s <provider> <instance> <zone> <aditional> <provider> <args> <proto>://<addr>", job.Name)
		}
		instance := job.Args[1]
		zone := job.Args[2]
		var cloud Cloud
		var err error
		switch job.Args[0] {
		case "gce":
			if len(job.Args) < 4 {
				return job.Errorf("usage: %s gce <instance> <zone> <project>")
			}
			cloud, err = NewCloudGCE(job.Args[3])
			if err != nil {
				job.Errorf("Unexpected error: %#v", err)
			}
		default:
			return job.Errorf("Unknown cloud provider: %s", job.Args[0])
		}
		ip, err := cloud.GetPublicIPAddress(instance, zone)
		instanceRunning := len(ip) > 0
		if !instanceRunning {
			log.Print("Instance doesn't exist, creating....")
			_, err = cloud.CreateInstance(instance, zone)
		}
		if err != nil {
			job.Errorf("Unexpected error: %#v", err)
		}
		remotePort := 8000
		localPort := 8001
		apiVersion := "v1.10"
		tunnelUrl, err := url.Parse(fmt.Sprintf("http://localhost:%d/%s/containers/json", localPort, apiVersion))
		if err != nil {
			return job.Errorf("Unexpected error: %#v", err)
		}
		tunnel := Tunnel{*tunnelUrl}

		if !tunnel.isActive() {
			fmt.Printf("Creating tunnel")
			_, err = cloud.OpenSecureTunnel(instance, zone, localPort, remotePort)
			if err != nil {
				job.Errorf("Failed to open tunnel: %#v", err)
			}
		}
		host := fmt.Sprintf("tcp://localhost:%d", localPort)
		client, err := newClient(host, apiVersion)
		if err != nil {
			job.Errorf("Unexpected error: %#v", err)
		}
		//job.Eng.Register("inspect", func(job *engine.Job) engine.Status {
		//	resp, err := client.call("GET", "/containers/
		job.Eng.Register("create", func(job *engine.Job) engine.Status {
			container := Container{
				Image: job.Getenv("Image"),
				Tty:   job.Getenv("Tty") == "true",
			}
			data, err := json.Marshal(container)
			resp, err := client.call("POST", "/containers/create", string(data))
			if err != nil {
				return job.Errorf("%s: post: %v", client.URL.String(), err)
			}
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				return job.Errorf("%s: read body: %#v", client.URL.String(), err)
			}
			var containerOut Container
			err = json.Unmarshal([]byte(body), &containerOut)
			_, err = job.Printf("%s\n", containerOut.Id)
			if err != nil {
				return job.Errorf("%s: write body: %#v", client.URL.String(), err)
			}
			log.Printf("%s", string(body))
			return engine.StatusOK
		})

		job.Eng.Register("start", func(job *engine.Job) engine.Status {
			path := fmt.Sprintf("/containers/%s/start", job.Args[0])
			resp, err := client.call("POST", path, "{\"Binds\":[],\"ContainerIDFile\":\"\",\"LxcConf\":[],\"Privileged\":false,\"PortBindings\":{},\"Links\":null,\"PublishAllPorts\":false,\"Dns\":null,\"DnsSearch\":[],\"VolumesFrom\":[]}")
			if err != nil {
				return job.Errorf("%s: post: %v", client.URL.String(), err)
			}
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				return job.Errorf("%s: read body: %#v", client.URL.String(), err)
			}
			log.Printf("%s", string(body))
			return engine.StatusOK
		})

		job.Eng.Register("containers", func(job *engine.Job) engine.Status {
			path := fmt.Sprintf(
				"/containers/json?all=%s&size=%s&since=%s&before=%s&limit=%s",
				url.QueryEscape(job.Getenv("all")),
				url.QueryEscape(job.Getenv("size")),
				url.QueryEscape(job.Getenv("since")),
				url.QueryEscape(job.Getenv("before")),
				url.QueryEscape(job.Getenv("limit")),
			)
			resp, err := client.call("GET", path, "")
			if err != nil {
				return job.Errorf("%s: get: %v", client.URL.String(), err)
			}
			// FIXME: check for response error
			c := engine.NewTable("Created", 0)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				return job.Errorf("%s: read body: %v", client.URL.String(), err)
			}
			fmt.Printf("---> '%s'\n", body)
			if _, err := c.ReadListFrom(body); err != nil {
				return job.Errorf("%s: readlist: %v", client.URL.String(), err)
			}
			c.WriteListTo(job.Stdout)
			return engine.StatusOK
		})

		job.Eng.Register("container_delete", func(job *engine.Job) engine.Status {
			log.Printf("%#v", job.Args)
			path := "/containers/" + job.Args[0]

			resp, err := client.call("DELETE", path, "")
			if err != nil {
				return job.Errorf("%s: delete: %v", client.URL.String(), err)
			}
			log.Printf("%#v", resp)
			return engine.StatusOK
		})

		job.Eng.Register("stop", func(job *engine.Job) engine.Status {
			log.Printf("%#v", job.Args)
			path := "/containers/" + job.Args[0] + "/stop"

			resp, err := client.call("POST", path, "")
			if err != nil {
				return job.Errorf("%s: delete: %v", client.URL.String(), err)
			}
			log.Printf("%#v", resp)
			return engine.StatusOK
		})

		job.Eng.RegisterCatchall(func(job *engine.Job) engine.Status {
			log.Printf("%#v %#v %#v", *job, job.Env(), job.Args)
			return engine.StatusOK
		})
		return engine.StatusOK
	})
	return nil
}
<commit_msg>Add some missing return statements<commit_after>//
// Copyright (C) 2013 The Docker Cloud authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package backends

import (
	"encoding/json"
	"fmt"
	"github.com/dotcloud/docker/engine"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
)

// The Cloud interface provides the contract that cloud providers should implement to enable
// running Docker containers in their cloud.
// TODO(bburns): Restructure this into Cloud, Instance and Tunnel interfaces
type Cloud interface {
	// GetPublicIPAddress returns the stringified address (e.g "1.2.3.4") of the runtime
	GetPublicIPAddress(name string, zone string) (string, error)

	// CreateInstance creates a virtual machine instance given a name and a zone.  Returns the
	// IP address of the instance.  Waits until Docker is up and functioning on the machine
	// before returning.
	CreateInstance(name string, zone string) (string, error)

	// DeleteInstance deletes a virtual machine instance, given the instance name and zone.
	DeleteInstance(name string, zone string) error

	// Open a secure tunnel (generally SSH) between the local host and a remote host.
	OpenSecureTunnel(name string, zone string, localPort int, remotePort int) (*os.Process, error)
}

func CloudBackend() engine.Installer {
	return &cloud{}
}

type cloud struct {
}

type Container struct {
	Id    string
	Image string
	Tty   bool
}

// returns true, if the connection was successful, false otherwise
type Tunnel struct {
	url.URL
}

func (t Tunnel) isActive() bool {
	_, err := http.Get(t.String())
	return err == nil
}

func (s *cloud) Install(eng *engine.Engine) error {
	eng.Register("cloud", func(job *engine.Job) engine.Status {
		if len(job.Args) < 3 {
			return job.Errorf("usage: %s <provider> <instance> <zone> <aditional> <provider> <args> <proto>://<addr>", job.Name)
		}
		instance := job.Args[1]
		zone := job.Args[2]
		var cloud Cloud
		var err error
		switch job.Args[0] {
		case "gce":
			if len(job.Args) < 4 {
				return job.Errorf("usage: %s gce <instance> <zone> <project>")
			}
			cloud, err = NewCloudGCE(job.Args[3])
			if err != nil {
				return job.Errorf("Unexpected error: %#v", err)
			}
		default:
			return job.Errorf("Unknown cloud provider: %s", job.Args[0])
		}
		ip, err := cloud.GetPublicIPAddress(instance, zone)
		instanceRunning := len(ip) > 0
		if !instanceRunning {
			log.Print("Instance doesn't exist, creating....")
			_, err = cloud.CreateInstance(instance, zone)
		}
		if err != nil {
			return job.Errorf("Unexpected error: %#v", err)
		}
		remotePort := 8000
		localPort := 8001
		apiVersion := "v1.10"
		tunnelUrl, err := url.Parse(fmt.Sprintf("http://localhost:%d/%s/containers/json", localPort, apiVersion))
		if err != nil {
			return job.Errorf("Unexpected error: %#v", err)
		}
		tunnel := Tunnel{*tunnelUrl}

		if !tunnel.isActive() {
			fmt.Printf("Creating tunnel")
			_, err = cloud.OpenSecureTunnel(instance, zone, localPort, remotePort)
			if err != nil {
				return job.Errorf("Failed to open tunnel: %#v", err)
			}
		}
		host := fmt.Sprintf("tcp://localhost:%d", localPort)
		client, err := newClient(host, apiVersion)
		if err != nil {
			return job.Errorf("Unexpected error: %#v", err)
		}
		//job.Eng.Register("inspect", func(job *engine.Job) engine.Status {
		//	resp, err := client.call("GET", "/containers/
		job.Eng.Register("create", func(job *engine.Job) engine.Status {
			container := Container{
				Image: job.Getenv("Image"),
				Tty:   job.Getenv("Tty") == "true",
			}
			data, err := json.Marshal(container)
			resp, err := client.call("POST", "/containers/create", string(data))
			if err != nil {
				return job.Errorf("%s: post: %v", client.URL.String(), err)
			}
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				return job.Errorf("%s: read body: %#v", client.URL.String(), err)
			}
			var containerOut Container
			err = json.Unmarshal([]byte(body), &containerOut)
			_, err = job.Printf("%s\n", containerOut.Id)
			if err != nil {
				return job.Errorf("%s: write body: %#v", client.URL.String(), err)
			}
			log.Printf("%s", string(body))
			return engine.StatusOK
		})

		job.Eng.Register("start", func(job *engine.Job) engine.Status {
			path := fmt.Sprintf("/containers/%s/start", job.Args[0])
			resp, err := client.call("POST", path, "{\"Binds\":[],\"ContainerIDFile\":\"\",\"LxcConf\":[],\"Privileged\":false,\"PortBindings\":{},\"Links\":null,\"PublishAllPorts\":false,\"Dns\":null,\"DnsSearch\":[],\"VolumesFrom\":[]}")
			if err != nil {
				return job.Errorf("%s: post: %v", client.URL.String(), err)
			}
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				return job.Errorf("%s: read body: %#v", client.URL.String(), err)
			}
			log.Printf("%s", string(body))
			return engine.StatusOK
		})

		job.Eng.Register("containers", func(job *engine.Job) engine.Status {
			path := fmt.Sprintf(
				"/containers/json?all=%s&size=%s&since=%s&before=%s&limit=%s",
				url.QueryEscape(job.Getenv("all")),
				url.QueryEscape(job.Getenv("size")),
				url.QueryEscape(job.Getenv("since")),
				url.QueryEscape(job.Getenv("before")),
				url.QueryEscape(job.Getenv("limit")),
			)
			resp, err := client.call("GET", path, "")
			if err != nil {
				return job.Errorf("%s: get: %v", client.URL.String(), err)
			}
			// FIXME: check for response error
			c := engine.NewTable("Created", 0)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				return job.Errorf("%s: read body: %v", client.URL.String(), err)
			}
			fmt.Printf("---> '%s'\n", body)
			if _, err := c.ReadListFrom(body); err != nil {
				return job.Errorf("%s: readlist: %v", client.URL.String(), err)
			}
			c.WriteListTo(job.Stdout)
			return engine.StatusOK
		})

		job.Eng.Register("container_delete", func(job *engine.Job) engine.Status {
			log.Printf("%#v", job.Args)
			path := "/containers/" + job.Args[0]

			resp, err := client.call("DELETE", path, "")
			if err != nil {
				return job.Errorf("%s: delete: %v", client.URL.String(), err)
			}
			log.Printf("%#v", resp)
			return engine.StatusOK
		})

		job.Eng.Register("stop", func(job *engine.Job) engine.Status {
			log.Printf("%#v", job.Args)
			path := "/containers/" + job.Args[0] + "/stop"

			resp, err := client.call("POST", path, "")
			if err != nil {
				return job.Errorf("%s: delete: %v", client.URL.String(), err)
			}
			log.Printf("%#v", resp)
			return engine.StatusOK
		})

		job.Eng.RegisterCatchall(func(job *engine.Job) engine.Status {
			log.Printf("%#v %#v %#v", *job, job.Env(), job.Args)
			return engine.StatusOK
		})
		return engine.StatusOK
	})
	return nil
}
<|endoftext|>