<commit_before>package serialapi

import (
	"encoding/binary"
	"errors"
	"fmt"
	"time"

	"github.com/bjyoungblood/gozw/zwave/frame"
	"github.com/bjyoungblood/gozw/zwave/protocol"
	"github.com/bjyoungblood/gozw/zwave/session"
)

type TransmitStatus struct {
	Status uint8
	TxTime uint16
}

func (s *SerialAPILayer) SendData(nodeId byte, payload []byte) (txTime uint16, err error) {

	transmitDone := make(chan bool)
	retStatus := make(chan error)
	txStatus := make(chan TransmitStatus)

	payload = append([]byte{nodeId, uint8(len(payload))}, payload...)
	payload = append(payload, protocol.TransmitOptionAck)

	request := &session.Request{
		FunctionId:       protocol.FnSendData,
		Payload:          payload,
		HasReturn:        true,
		ReceivesCallback: true,
		Lock:             true,
		Release:          transmitDone,
		Timeout:          10 * time.Second,

		ReturnCallback: func(err error, ret *frame.Frame) bool {
			if ret.Payload[1] == 0 {
				transmitDone <- true
				retStatus <- errors.New("SendData: transmit buffer overflow")
			} else {
				retStatus <- nil
			}

			return true
		},

		Callback: func(cbFrame frame.Frame) {
			status := TransmitStatus{}
			status.Status = cbFrame.Payload[2]
			if len(cbFrame.Payload) == 5 {
				status.TxTime = binary.BigEndian.Uint16(cbFrame.Payload[3:5])
			}

			txStatus <- status
		},
	}

	s.sessionLayer.MakeRequest(request)

	err = <-retStatus
	if err != nil {
		return
	}

	status := <-txStatus
	switch status.Status {
	case protocol.TransmitCompleteOk:
		return status.TxTime, nil
	case protocol.TransmitCompleteNoAck:
		return status.TxTime, errors.New("Transmit complete: no ack from destination")
	case protocol.TransmitCompleteFail:
		return status.TxTime, errors.New("Transmit failure: network busy/jammed")
	case protocol.TransmitRoutingNotIdle:
		return status.TxTime, errors.New("Transmit failure: routing not idle")
	case protocol.TransmitCompleteNoRoute:
		return status.TxTime, errors.New("Transmit complete: no route")
	default:
		return status.TxTime, fmt.Errorf("Unknown tranmission status: %d", status.Status)
	}
}
<commit_msg>Send data releases session lock after receiving callback<commit_after>package serialapi

import (
	"encoding/binary"
	"errors"
	"fmt"
	"time"

	"github.com/bjyoungblood/gozw/zwave/frame"
	"github.com/bjyoungblood/gozw/zwave/protocol"
	"github.com/bjyoungblood/gozw/zwave/session"
)

type TransmitStatus struct {
	Status uint8
	TxTime uint16
}

func (s *SerialAPILayer) SendData(nodeId byte, payload []byte) (txTime uint16, err error) {

	transmitDone := make(chan bool)
	retStatus := make(chan error)
	txStatus := make(chan TransmitStatus)

	payload = append([]byte{nodeId, uint8(len(payload))}, payload...)
	payload = append(payload, protocol.TransmitOptionAck)

	request := &session.Request{
		FunctionId:       protocol.FnSendData,
		Payload:          payload,
		HasReturn:        true,
		ReceivesCallback: true,
		Lock:             true,
		Release:          transmitDone,
		Timeout:          10 * time.Second,

		ReturnCallback: func(err error, ret *frame.Frame) bool {
			if ret.Payload[1] == 0 {
				transmitDone <- true
				retStatus <- errors.New("SendData: transmit buffer overflow")
			} else {
				retStatus <- nil
			}

			return true
		},

		Callback: func(cbFrame frame.Frame) {
			status := TransmitStatus{}
			status.Status = cbFrame.Payload[2]
			if len(cbFrame.Payload) == 5 {
				status.TxTime = binary.BigEndian.Uint16(cbFrame.Payload[3:5])
			}

			transmitDone <- true
			txStatus <- status
		},
	}

	s.sessionLayer.MakeRequest(request)

	err = <-retStatus
	if err != nil {
		return
	}

	status := <-txStatus
	switch status.Status {
	case protocol.TransmitCompleteOk:
		return status.TxTime, nil
	case protocol.TransmitCompleteNoAck:
		return status.TxTime, errors.New("Transmit complete: no ack from destination")
	case protocol.TransmitCompleteFail:
		return status.TxTime, errors.New("Transmit failure: network busy/jammed")
	case protocol.TransmitRoutingNotIdle:
		return status.TxTime, errors.New("Transmit failure: routing not idle")
	case protocol.TransmitCompleteNoRoute:
		return status.TxTime, errors.New("Transmit complete: no route")
	default:
		return status.TxTime, fmt.Errorf("Unknown tranmission status: %d", status.Status)
	}
}
<|endoftext|>


<commit_before>package chef

import (
	"bytes"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
)

// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"

// Body wraps io.Reader and adds methods for calculating hashes and detecting content
type Body struct {
	io.Reader
}

// AuthConfig representing a client and a private key used for encryption
//  This is embedded in the Client type
type AuthConfig struct {
	PrivateKey *rsa.PrivateKey
	ClientName string
}

// Client is vessel for public methods used against the chef-server
type Client struct {
	Auth    *AuthConfig
	BaseURL *url.URL
	client  *http.Client

	ACLs         *ACLService
	Cookbooks    *CookbookService
	DataBags     *DataBagService
	Environments *EnvironmentService
	Nodes        *NodeService
	Roles        *RoleService
	Sandboxes    *SandboxService
	Search       *SearchService
}

// Config contains the configuration options for a chef client. This is Used primarily in the NewClient() constructor in order to setup a proper client object
type Config struct {
	// This should be the user ID on the chef server
	Name string

	// This is the plain text private Key for the user
	Key string

	// BaseURL is the chef server URL used to connect too. Is using orgs you should include your org in the url
	BaseURL string

	// When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true
	SkipSSL bool
}

/*
An ErrorResponse reports one or more errors caused by an API request.
Thanks to https://github.com/google/go-github
*/
type ErrorResponse struct {
	Response *http.Response // HTTP response that caused this error
}

// Buffer creates a  byte.Buffer copy from a io.Reader resets read on reader to 0,0
func (body *Body) Buffer() *bytes.Buffer {
	var b bytes.Buffer
	if body.Reader == nil {
		return &b
	}

	b.ReadFrom(body.Reader)
	_, err := body.Reader.(io.Seeker).Seek(0, 0)
	if err != nil {
		log.Fatal(err)
	}
	return &b
}

// Hash calculates the body content hash
func (body *Body) Hash() (h string) {
	b := body.Buffer()
	// empty buffs should return a empty string
	if b.Len() == 0 {
		h = HashStr("")
	}
	h = HashStr(b.String())
	return
}

// ContentType returns the content-type string of Body as detected by http.DetectContentType()
func (body *Body) ContentType() string {
	if json.Unmarshal(body.Buffer().Bytes(), &struct{}{}) == nil {
		return "application/json"
	}
	return http.DetectContentType(body.Buffer().Bytes())
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode)
}

// NewClient is the client generator used to instantiate a client for talking to a chef-server
// It is a simple constructor for the Client struct intended as a easy interface for issuing
// signed requests
func NewClient(cfg *Config) (*Client, error) {
	pk, err := PrivateKeyFromString([]byte(cfg.Key))
	if err != nil {
		return nil, err
	}

	baseUrl, _ := url.Parse(cfg.BaseURL)

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.SkipSSL},
	}

	c := &Client{
		Auth: &AuthConfig{
			PrivateKey: pk,
			ClientName: cfg.Name,
		},
		client:  &http.Client{Transport: tr},
		BaseURL: baseUrl,
	}
	c.ACLs = &ACLService{client: c}
	c.Cookbooks = &CookbookService{client: c}
	c.DataBags = &DataBagService{client: c}
	c.Environments = &EnvironmentService{client: c}
	c.Nodes = &NodeService{client: c}
	c.Roles = &RoleService{client: c}
	c.Sandboxes = &SandboxService{client: c}
	c.Search = &SearchService{client: c}
	return c, nil
}

// magicRequestDecoder performs a request on an endpoint, and decodes the response into the passed in Type
func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v interface{}) error {
	req, err := c.NewRequest(method, path, body)
	if err != nil {
		return err
	}

	debug("Request: %+v \n", req)
	res, err := c.Do(req, v)
	debug("Response: %+v \n", res)
	if err != nil {
		return err
	}
	return err
}

// NewRequest returns a signed request  suitable for the chef server
func (c *Client) NewRequest(method string, requestUrl string, body io.Reader) (*http.Request, error) {
	relativeUrl, err := url.Parse(requestUrl)
	if err != nil {
		return nil, err
	}
	u := c.BaseURL.ResolveReference(relativeUrl)

	// NewRequest uses a new value object of body
	req, err := http.NewRequest(method, u.String(), body)
	if err != nil {
		return nil, err
	}

	// parse and encode Querystring Values
	values := req.URL.Query()
	req.URL.RawQuery = values.Encode()
	debug("Encoded url %+v", u)

	myBody := &Body{body}

	if body != nil {
		// Detect Content-type
		req.Header.Set("Content-Type", myBody.ContentType())
	}

	// Calculate the body hash
	req.Header.Set("X-Ops-Content-Hash", myBody.Hash())

	// don't have to check this works, signRequest only emits error when signing hash is not valid, and we baked that in
	c.Auth.SignRequest(req)
	return req, nil
}

// CheckResponse receives a pointer to a http.Response and generates an Error via unmarshalling
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}
	return errorResponse
}

// Do is used either internally via our magic request shite or a user may use it
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	// BUG(fujin) tightly coupled
	err = CheckResponse(res) // <--
	if err != nil {
		return res, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, res.Body)
		} else {
			err = json.NewDecoder(res.Body).Decode(v)
			if err != nil {
				return res, err
			}
		}
	}
	return res, nil
}

// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request) error {
	// sanitize the path for the chef-server
	// chef-server doesn't support '//' in the Hash Path.
	var endpoint string
	if request.URL.Path != "" {
		endpoint = path.Clean(request.URL.Path)
		request.URL.Path = endpoint
	} else {
		endpoint = request.URL.Path
	}

	request.Header.Set("Method", request.Method)
	request.Header.Set("Hashed Path", HashStr(endpoint))
	request.Header.Set("Accept", "application/json")
	request.Header.Set("X-Chef-Version", ChefVersion)
	request.Header.Set("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
	request.Header.Set("X-Ops-UserId", ac.ClientName)
	request.Header.Set("X-Ops-Sign", "algorithm=sha1;version=1.0")

	// To validate the signature it seems to be very particular
	var content string
	for _, key := range []string{"Method", "Hashed Path", "X-Ops-Content-Hash", "X-Ops-Timestamp", "X-Ops-UserId"} {
		content += fmt.Sprintf("%s:%s\n", key, request.Header.Get(key))
	}
	content = strings.TrimSuffix(content, "\n")
	// generate signed string of headers
	// Since we've gone through additional validation steps above,
	// we shouldn't get an error at this point
	signature, err := GenerateSignature(ac.PrivateKey, content)
	if err != nil {
		return err
	}

	// TODO: THIS IS CHEF PROTOCOL SPECIFIC
	// Signature is made up of n 60 length chunks
	base64sig := Base64BlockEncode(signature, 60)

	// roll over the auth slice and add the apropriate header
	for index, value := range base64sig {
		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
	}

	return nil
}

// PrivateKeyFromString parses an RSA private key from a string
func PrivateKeyFromString(key []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, fmt.Errorf("block size invalid for '%s'", string(key))
	}
	rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return rsaKey, nil
}
<commit_msg>Added a Timeout config option to the http.Client, which defaults to no timeout<commit_after>package chef

import (
	"bytes"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
)

// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"

// Body wraps io.Reader and adds methods for calculating hashes and detecting content
type Body struct {
	io.Reader
}

// AuthConfig representing a client and a private key used for encryption
//  This is embedded in the Client type
type AuthConfig struct {
	PrivateKey *rsa.PrivateKey
	ClientName string
}

// Client is vessel for public methods used against the chef-server
type Client struct {
	Auth    *AuthConfig
	BaseURL *url.URL
	client  *http.Client

	ACLs         *ACLService
	Cookbooks    *CookbookService
	DataBags     *DataBagService
	Environments *EnvironmentService
	Nodes        *NodeService
	Roles        *RoleService
	Sandboxes    *SandboxService
	Search       *SearchService
}

// Config contains the configuration options for a chef client. This is Used primarily in the NewClient() constructor in order to setup a proper client object
type Config struct {
	// This should be the user ID on the chef server
	Name string

	// This is the plain text private Key for the user
	Key string

	// BaseURL is the chef server URL used to connect too. Is using orgs you should include your org in the url
	BaseURL string

	// When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true
	SkipSSL bool

	// Time to wait in seconds before giving up on a request to the server
	Timeout time.Duration
}

/*
An ErrorResponse reports one or more errors caused by an API request.
Thanks to https://github.com/google/go-github
*/
type ErrorResponse struct {
	Response *http.Response // HTTP response that caused this error
}

// Buffer creates a  byte.Buffer copy from a io.Reader resets read on reader to 0,0
func (body *Body) Buffer() *bytes.Buffer {
	var b bytes.Buffer
	if body.Reader == nil {
		return &b
	}

	b.ReadFrom(body.Reader)
	_, err := body.Reader.(io.Seeker).Seek(0, 0)
	if err != nil {
		log.Fatal(err)
	}
	return &b
}

// Hash calculates the body content hash
func (body *Body) Hash() (h string) {
	b := body.Buffer()
	// empty buffs should return a empty string
	if b.Len() == 0 {
		h = HashStr("")
	}
	h = HashStr(b.String())
	return
}

// ContentType returns the content-type string of Body as detected by http.DetectContentType()
func (body *Body) ContentType() string {
	if json.Unmarshal(body.Buffer().Bytes(), &struct{}{}) == nil {
		return "application/json"
	}
	return http.DetectContentType(body.Buffer().Bytes())
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode)
}

// NewClient is the client generator used to instantiate a client for talking to a chef-server
// It is a simple constructor for the Client struct intended as a easy interface for issuing
// signed requests
func NewClient(cfg *Config) (*Client, error) {
	pk, err := PrivateKeyFromString([]byte(cfg.Key))
	if err != nil {
		return nil, err
	}

	baseUrl, _ := url.Parse(cfg.BaseURL)

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.SkipSSL},
	}

	c := &Client{
		Auth: &AuthConfig{
			PrivateKey: pk,
			ClientName: cfg.Name,
		},
		client: &http.Client{
			Transport: tr,
			Timeout:   cfg.Timeout * time.Second,
		},
		BaseURL: baseUrl,
	}
	c.ACLs = &ACLService{client: c}
	c.Cookbooks = &CookbookService{client: c}
	c.DataBags = &DataBagService{client: c}
	c.Environments = &EnvironmentService{client: c}
	c.Nodes = &NodeService{client: c}
	c.Roles = &RoleService{client: c}
	c.Sandboxes = &SandboxService{client: c}
	c.Search = &SearchService{client: c}
	return c, nil
}

// magicRequestDecoder performs a request on an endpoint, and decodes the response into the passed in Type
func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v interface{}) error {
	req, err := c.NewRequest(method, path, body)
	if err != nil {
		return err
	}

	debug("Request: %+v \n", req)
	res, err := c.Do(req, v)
	debug("Response: %+v \n", res)
	if err != nil {
		return err
	}
	return err
}

// NewRequest returns a signed request  suitable for the chef server
func (c *Client) NewRequest(method string, requestUrl string, body io.Reader) (*http.Request, error) {
	relativeUrl, err := url.Parse(requestUrl)
	if err != nil {
		return nil, err
	}
	u := c.BaseURL.ResolveReference(relativeUrl)

	// NewRequest uses a new value object of body
	req, err := http.NewRequest(method, u.String(), body)
	if err != nil {
		return nil, err
	}

	// parse and encode Querystring Values
	values := req.URL.Query()
	req.URL.RawQuery = values.Encode()
	debug("Encoded url %+v", u)

	myBody := &Body{body}

	if body != nil {
		// Detect Content-type
		req.Header.Set("Content-Type", myBody.ContentType())
	}

	// Calculate the body hash
	req.Header.Set("X-Ops-Content-Hash", myBody.Hash())

	// don't have to check this works, signRequest only emits error when signing hash is not valid, and we baked that in
	c.Auth.SignRequest(req)
	return req, nil
}

// CheckResponse receives a pointer to a http.Response and generates an Error via unmarshalling
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}
	return errorResponse
}

// Do is used either internally via our magic request shite or a user may use it
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	// BUG(fujin) tightly coupled
	err = CheckResponse(res) // <--
	if err != nil {
		return res, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, res.Body)
		} else {
			err = json.NewDecoder(res.Body).Decode(v)
			if err != nil {
				return res, err
			}
		}
	}
	return res, nil
}

// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request) error {
	// sanitize the path for the chef-server
	// chef-server doesn't support '//' in the Hash Path.
	var endpoint string
	if request.URL.Path != "" {
		endpoint = path.Clean(request.URL.Path)
		request.URL.Path = endpoint
	} else {
		endpoint = request.URL.Path
	}

	request.Header.Set("Method", request.Method)
	request.Header.Set("Hashed Path", HashStr(endpoint))
	request.Header.Set("Accept", "application/json")
	request.Header.Set("X-Chef-Version", ChefVersion)
	request.Header.Set("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
	request.Header.Set("X-Ops-UserId", ac.ClientName)
	request.Header.Set("X-Ops-Sign", "algorithm=sha1;version=1.0")

	// To validate the signature it seems to be very particular
	var content string
	for _, key := range []string{"Method", "Hashed Path", "X-Ops-Content-Hash", "X-Ops-Timestamp", "X-Ops-UserId"} {
		content += fmt.Sprintf("%s:%s\n", key, request.Header.Get(key))
	}
	content = strings.TrimSuffix(content, "\n")
	// generate signed string of headers
	// Since we've gone through additional validation steps above,
	// we shouldn't get an error at this point
	signature, err := GenerateSignature(ac.PrivateKey, content)
	if err != nil {
		return err
	}

	// TODO: THIS IS CHEF PROTOCOL SPECIFIC
	// Signature is made up of n 60 length chunks
	base64sig := Base64BlockEncode(signature, 60)

	// roll over the auth slice and add the apropriate header
	for index, value := range base64sig {
		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
	}

	return nil
}

// PrivateKeyFromString parses an RSA private key from a string
func PrivateKeyFromString(key []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, fmt.Errorf("block size invalid for '%s'", string(key))
	}
	rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return rsaKey, nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"strings"

	"github.com/shurcooL/go-goon"
)

func main() {
	activeFileManager := NewActiveFileManager()

	webHandler := getWebHandler(activeFileManager)

	err := http.ListenAndServe(":27080", webHandler)

	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

	// TODO: TLS
	err = http.ListenAndServe(":27443", webHandler)

	if err != nil {
		log.Fatal("ListenAndServeTLS: ", err)
	}
}

func getWebHandler(activeFileManager *ActiveFileManager) http.Handler {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		method := req.Method
		path := urlPathToArray(req.URL.Path)

		switch {
		case len(path) == 1:
			if method == "GET" {
				// request for a file
			} else if method == "PUT" {
				// uploading a file
			} else {
				http.Error(res, "Method Not Allowed", 405)
			}
		case len(path) == 2 && path[0] == "api" && path[1] == "getid" && method == "GET":
			goon.Dump(req)

			newFileId := activeFileManager.PrepareUpload(GenerateNewFileID, "USERKEYTODO")

			log.Println("/api/getid returning", newFileId)

			res.Write([]byte(newFileId))
		default:
			http.Error(res, "Not Found", 404)
		}
	})
}

func urlPathToArray(path string) []string {
	var pathComponents []string

	for _, pathComponent := range strings.Split(path, "/") {
		if len(pathComponent) > 0 {
			pathComponents = append(pathComponents, pathComponent)
		}
	}

	return pathComponents
}
<commit_msg>code review comments from @shurcooL<commit_after>package main

import (
	"log"
	"net/http"
	"strings"

	"github.com/shurcooL/go-goon"
)

func main() {
	activeFileManager := NewActiveFileManager()

	webHandler := getWebHandler(activeFileManager)

	err := http.ListenAndServe(":27080", webHandler)

	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

	// TODO: TLS
	err = http.ListenAndServe(":27443", webHandler)

	if err != nil {
		log.Fatal("ListenAndServeTLS: ", err)
	}
}

func getWebHandler(activeFileManager *ActiveFileManager) http.Handler {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		method := req.Method
		path := urlPathToArray(req.URL.Path)

		switch {
		case len(path) == 1:
			if method == "GET" {
				// request for a file
			} else if method == "PUT" {
				// uploading a file
			} else {
				http.Error(res, "Method Not Allowed", http.StatusMethodNotAllowed)
			}
		case len(path) == 2 && path[0] == "api" && path[1] == "getid" && method == "GET":
			goon.Dump(req)

			newFileId := activeFileManager.PrepareUpload(GenerateNewFileID, "USERKEYTODO")

			log.Println("/api/getid returning", newFileId)

			res.Write([]byte(newFileId))
		default:
			http.NotFound(res, req)
		}
	})
}

func urlPathToArray(path string) []string {
	splitPath := strings.Split(path, "/")

	startIdx := 0

	if len(splitPath) >= 1 && splitPath[startIdx] == "" {
		startIdx += 1
	}

	endIdx := len(splitPath) - 1

	if len(splitPath) >= 1 && splitPath[endIdx] == "" {
		endIdx -= 1
	}

	return splitPath[startIdx : endIdx+1]
}
<|endoftext|>


<commit_before>package ext

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
)

func PerformHttpRequest(r http.Handler, method, path string, header http.Header, reqBody io.Reader) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, reqBody)
	if header != nil {
		req.Header = header
	}
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func DecodeJSON(r io.Reader) (map[string]interface{}, error) {
	var m map[string]interface{}
	decoder := json.NewDecoder(r)
	err := decoder.Decode(&m)
	if err != nil {
		return nil, err
	}

	if len(m) <= 0 {
		return nil, errors.New("ext.DecodeJSON: no content")
	}

	return m, nil
}

func ReadMockFile(filepath string) string {
	file, err := os.Open(filepath) // For read access.
	if err != nil {
		return ""
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		return ""
	}
	sdata := string(data)
	return sdata
}
<commit_msg>TestOverwriteAllVersions<commit_after>package ext

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
)

func PerformHttpRequest(r http.Handler, method, path string, header http.Header, reqBody io.Reader) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, reqBody)
	if header != nil {
		req.Header = header
	}
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func DecodeJSON(r io.Reader) (map[string]interface{}, error) {
	var m map[string]interface{}
	decoder := json.NewDecoder(r)
	err := decoder.Decode(&m)
	if err != nil {
		return nil, err
	}

	if len(m) <= 0 {
		return nil, errors.New("ext.DecodeJSON: no content")
	}

	return m, nil
}

func DecodeJSONSlice(r io.Reader) ([]map[string]interface{}, error) {
	var m []map[string]interface{}
	decoder := json.NewDecoder(r)
	err := decoder.Decode(&m)
	if err != nil {
		return nil, err
	}

	if len(m) <= 0 {
		return nil, errors.New("ext.DecodeJSONSlice: no content")
	}

	return m, nil
}

func ReadMockFile(filepath string) string {
	file, err := os.Open(filepath) // For read access.
	if err != nil {
		return ""
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		return ""
	}
	sdata := string(data)
	return sdata
}

func Pre(handlers map[string]http.HandlerFunc) {

	return
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/arschles/eiger/lib/util"
	"time"
)

//the multiplier for heartbeat duration. used to determine when a heartbeat
//has timed out
const HB_TIMEOUT_MULTIPLIER = 10

type HeartbeatLoop struct {
	lookup   *AgentLookup
	hbDur    time.Duration
	notifyCh chan Agent
}

func NewHeartbeatLoop(l *AgentLookup, hbDur time.Duration) *HeartbeatLoop {
	notifyCh := make(chan Agent)
	loop := HeartbeatLoop{l, hbDur, notifyCh}

	go loop.run()

	return &loop
}

//Notify tells the heartbeat loop that an agent has either heartbeated
//or has been added
func (h *HeartbeatLoop) Notify(a Agent) {
	h.notifyCh <- a
}

func (h *HeartbeatLoop) agentWatcher(agent Agent, ticker <-chan bool) {
	for {
		start := time.Now()
		select {
		case <-ticker:
			if time.Since(start) > h.hbDur*4 {
				util.LogWarnf("(late heartbeat) removing agent %s from alive set", agent)
				h.lookup.Remove(agent)
				return
			}
		case <-time.After(h.hbDur * HB_TIMEOUT_MULTIPLIER):
			util.LogWarnf("(heartbeat timeout) removing agent %s from alive set", agent)
			h.lookup.Remove(agent)
			return
		}
	}
}

func (h *HeartbeatLoop) run() {
	tickers := map[Agent]chan bool{}
	for {
		select {
		case agent := <-h.notifyCh:
			tickerCh, ok := tickers[agent]
			if !ok {
				t := make(chan bool)
				tickers[agent] = t
				tickerCh = t
			}
			go h.agentWatcher(agent, tickerCh)
			go func() {
				tickerCh <- true
			}()
		}
	}
}
<commit_msg>removing the heartbeat loop it's not used anymore<commit_after><|endoftext|>


<commit_before>package game

func init() {
  registerActionType("chain attack", &ActionChainAttack{})
}
type ActionChainAttack struct {
  basicIcon
  Ent   *Entity

  Power int
  Cost  int
  Range int
  Melee int
  Adds  int

  targets map[*Entity]bool
  marks   []*Entity
}

func (a *ActionChainAttack) Prep() bool {
  if a.Ent.CurAp() < a.Cost {
    return false
  }

  targets := getEntsWithinRange(a.Ent, a.Range, a.Ent.level)
  if len(targets) == 0 {
    return false
  }

  a.targets = make(map[*Entity]bool, len(a.targets))
  a.marks = nil
  for _,target := range targets {
    a.targets[target] = true
    a.Ent.level.GetCellAtPos(target.pos).highlight |= Attackable
  }
  return true
}

func (a *ActionChainAttack) Cancel() {
  a.marks = nil
  a.targets = nil
  a.Ent.level.clearCache(Attackable | Targeted)
}

func (a *ActionChainAttack) MouseOver(bx,by float64) {
}

func (a *ActionChainAttack) MouseClick(bx,by float64) bool {
  t := findTargetOnClick(bx, by, a.Ent.level, a.targets)
  if t == nil { return false }
  a.Ent.level.GetCellAtPos(t.pos).highlight |= Targeted
  a.marks = append(a.marks, t)

  if len(a.marks) == a.Adds {
    a.Ent.SpendAp(a.Cost)
    return true
  }
  return false
}

func (a *ActionChainAttack) Maintain(dt int64) bool {
  if len(a.marks) == 0 {
    a.Cancel()
    return true
  }

  mark := a.marks[0]
  for _,ent := range []*Entity{ a.Ent, mark } {
    if ent.s.NumPendingCommands() != 0 { return false }
    if ent.s.CurAnim() != "ready" { return false }
  }

  a.marks = a.marks[1 : ]

  if a.Melee != 0 {
    a.Ent.s.Command("melee")
  } else {
    a.Ent.s.Command("ranged")
  }

  attack := a.Power + a.Ent.CurAttack() + ((Dice("5d5") - 2) / 3 - 4)
  defense := mark.CurDefense()

  mark.s.Command("defend")
  if attack <= defense {
    mark.s.Command("undamaged")

    // Chain attacks only continue after successful attacks
    a.Cancel()
    return true
  } else {
    mark.DoDamage(attack - defense)
    if mark.CurHealth() <= 0 {
      mark.s.Command("killed")
    } else {
      mark.s.Command("damaged")
    }
  }

  a.Ent.turnToFace(mark.pos)

  return false
}
<commit_msg>Fixed stall bug in chain attacks<commit_after>package game

func init() {
  registerActionType("chain attack", &ActionChainAttack{})
}
type ActionChainAttack struct {
  basicIcon
  Ent   *Entity

  Power int
  Cost  int
  Range int
  Melee int
  Adds  int

  targets map[*Entity]bool
  marks   []*Entity
}

func (a *ActionChainAttack) Prep() bool {
  if a.Ent.CurAp() < a.Cost {
    return false
  }

  targets := getEntsWithinRange(a.Ent, a.Range, a.Ent.level)
  if len(targets) == 0 {
    return false
  }

  a.targets = make(map[*Entity]bool, len(a.targets))
  a.marks = nil
  for _,target := range targets {
    a.targets[target] = true
    a.Ent.level.GetCellAtPos(target.pos).highlight |= Attackable
  }
  return true
}

func (a *ActionChainAttack) Cancel() {
  a.marks = nil
  a.targets = nil
  a.Ent.level.clearCache(Attackable | Targeted)
}

func (a *ActionChainAttack) MouseOver(bx,by float64) {
}

func (a *ActionChainAttack) MouseClick(bx,by float64) bool {
  t := findTargetOnClick(bx, by, a.Ent.level, a.targets)
  if t == nil { return false }
  a.Ent.level.GetCellAtPos(t.pos).highlight |= Targeted
  a.marks = append(a.marks, t)

  if len(a.marks) == a.Adds {
    a.Ent.SpendAp(a.Cost)
    return true
  }
  return false
}

func (a *ActionChainAttack) Maintain(dt int64) bool {
  if len(a.marks) == 0 {
    a.Cancel()
    return true
  }

  mark := a.marks[0]
  for _,ent := range []*Entity{ a.Ent, mark } {
    if ent.s.NumPendingCommands() != 0 { return false }
    if ent.s.CurState() == "killed" {
      // The mark may have already died from a previous attack in this chain,
      // in that case we just skip this entity
      a.marks = a.marks[1 : ]
      return a.Maintain(dt)
    }
    if ent.s.CurAnim() != "ready" { return false }
  }

  a.marks = a.marks[1 : ]

  if a.Melee != 0 {
    a.Ent.s.Command("melee")
  } else {
    a.Ent.s.Command("ranged")
  }

  attack := a.Power + a.Ent.CurAttack() + ((Dice("5d5") - 2) / 3 - 4)
  defense := mark.CurDefense()

  mark.s.Command("defend")
  if attack <= defense {
    mark.s.Command("undamaged")

    // Chain attacks only continue after successful attacks
    a.Cancel()
    return true
  } else {
    mark.DoDamage(attack - defense)
    if mark.CurHealth() <= 0 {
      mark.s.Command("killed")
    } else {
      mark.s.Command("damaged")
    }
  }

  a.Ent.turnToFace(mark.pos)

  return false
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface"
	"github.com/cloudfoundry-community/go-cfclient"
	"net/url"
	"regexp"
	"strings"
	"time"

	"code.cloudfoundry.org/lager"
	awsec "github.com/aws/aws-sdk-go/service/elasticache"

	"github.com/alphagov/paas-cf/tools/metrics/pkg/elasticache"

	m "github.com/alphagov/paas-cf/tools/metrics/pkg/metrics"
)

const NonClusteredIdPattern = "[a-z0-9]+-\\d+"
const ClusteredIdPattern = "[a-z0-9]+-\\d+-\\d+"

type RedisServiceDetails struct {
	ServiceInstance cfclient.ServiceInstance
	Space           cfclient.Space
	Org             cfclient.Org
}

func ElasticCacheInstancesGauge(
	logger lager.Logger,
	ecs *elasticache.ElasticacheService,
	cfAPI cfclient.CloudFoundryClient,
	clusterIdHashingFunction elasticache.ElasticacheClusterIdHashingFunction,
	interval time.Duration,
) m.MetricReadCloser {
	return m.NewMetricPoller(interval, func(w m.MetricWriter) error {
		lsess := logger.Session("elasticache-gauges")
		redisServiceDetails, err := fetchRedisServiceInstances(cfAPI)

		if err != nil {
			return err
		}

		var metrics []m.Metric

		cacheParameterGroupCount := 0
		err = iterateCacheParameterGroups(
			ecs.Client,
			func(cacheParameterGroup *awsec.CacheParameterGroup) {
				if !strings.HasPrefix(*cacheParameterGroup.CacheParameterGroupName, "default.") {
					cacheParameterGroupCount++
				}
			},
		)

		if err != nil {
			return err
		}

		metrics = append(metrics, m.Metric{
			Kind:  m.Gauge,
			Time:  time.Now(),
			Name:  "aws.elasticache.cache_parameter_group.count",
			Value: float64(cacheParameterGroupCount),
			Unit:  "count",
		})
		lsess.Info("metric-exported", lager.Data{"metric": metrics[len(metrics)-1]})

		svcGuidToDetails := map[string]RedisServiceDetails{}
		clusterIdToSvcGuid := map[string]string{}
		for _, details := range redisServiceDetails {
			svcGuidToDetails[details.ServiceInstance.Guid] = details
			clusterIdToSvcGuid[clusterIdHashingFunction(details.ServiceInstance.Guid)] = details.ServiceInstance.Guid
		}

		lsess.Info("svgguid-to-details", lager.Data{"map": svcGuidToDetails})

		nodeCount := int64(0)
		err = iterateCacheClusterPages(
			ecs.Client,
			func(cacheCluster *awsec.CacheCluster) {
				nodeCount = nodeCount + *cacheCluster.NumCacheNodes
				userSuppliedClusterId := cleanClusterId(cacheCluster)
				realClusterId := aws.StringValue(cacheCluster.CacheClusterId)

				svcGuid, ok := clusterIdToSvcGuid[userSuppliedClusterId]
				if !ok {
					e := fmt.Errorf("unable to find service guid for cluster with user supplied name of %s", userSuppliedClusterId)
					lsess.Error("service-guid-not-found", e, lager.Data{
						"real_cluster_id":          realClusterId,
						"user_supplied_cluster_id": userSuppliedClusterId,
					})
					return e
				}
				details, ok := svcGuidToDetails[svcGuid]
				if !ok {
					e := fmt.Errorf("unable to find details of service %s", svcGuid)
					lsess.Error("details-not-found", e, lager.Data{"real_cluster_id": realClusterId, "service_guid": svcGuid})
					return e
				}

				lsess.Info("redis-service-details-found", lager.Data{"details": details})

				metrics = append(metrics, m.Metric{
					Kind:  m.Gauge,
					Time:  time.Now(),
					Name:  "aws.elasticache.cluster.nodes.count",
					Value: float64(*cacheCluster.NumCacheNodes),
					Unit:  "count",
					Tags: m.MetricTags{
						{Label: "cluster_id", Value: userSuppliedClusterId},
						{Label: "elasticache_cluster_id", Value: realClusterId},
						{Label: "service_instance_guid", Value: details.ServiceInstance.Guid},
						{Label: "space_name", Value: details.Space.Name},
						{Label: "space_guid", Value: details.Space.Guid},
						{Label: "org_name", Value: details.Org.Name},
						{Label: "org_guid", Value: details.Org.Guid},
					},
				})

				lsess.Info("metric-exported", lager.Data{"metric": metrics[len(metrics)-1]})
			},
		)
		if err != nil {
			return err
		}

		metrics = append(metrics, m.Metric{
			Kind:  m.Gauge,
			Time:  time.Now(),
			Name:  "aws.elasticache.node.count",
			Value: float64(nodeCount),
			Unit:  "count",
		})
		lsess.Info("metric-exported", lager.Data{"metric": metrics[len(metrics)-1]})

		return w.WriteMetrics(metrics)
	})
}

func fetchRedisServiceInstances(cfAPI cfclient.CloudFoundryClient) ([]RedisServiceDetails, error) {
	spacesCache := map[string]cfclient.Space{}
	orgsCache := map[string]cfclient.Org{}

	servicesWithRedisLabel, err := cfAPI.ListServicesByQuery(url.Values{
		"q": []string{"label:redis"},
	})

	if err != nil {
		return []RedisServiceDetails{}, err
	}

	if len(servicesWithRedisLabel) == 0 {
		return nil, fmt.Errorf("could not find service with label=redis")
	}

	redisServiceGuid := servicesWithRedisLabel[0].Guid
	redisServicePlans, err := cfAPI.ListServicePlansByQuery(url.Values{
		"q": []string{"service_guid:" + redisServiceGuid},
	})

	if err != nil {
		return []RedisServiceDetails{}, err
	}

	var serviceDetails []RedisServiceDetails
	for _, plan := range redisServicePlans {
		planInstances, err := cfAPI.ListServiceInstancesByQuery(url.Values{
			"q": []string{"service_plan_guid:" + plan.Guid},
		})

		if err != nil {
			return []RedisServiceDetails{}, err
		}

		for _, instance := range planInstances {
			var space cfclient.Space
			if s, ok := spacesCache[instance.SpaceGuid]; ok {
				space = s
			} else {
				space, err = cfAPI.GetSpaceByGuid(instance.SpaceGuid)
				if err != nil {
					return []RedisServiceDetails{}, err
				}

				spacesCache[space.Guid] = space
			}

			var org cfclient.Org
			if o, ok := orgsCache[space.OrganizationGuid]; ok {
				org = o
			} else {
				org, err = cfAPI.GetOrgByGuid(space.OrganizationGuid)
				if err != nil {
					return []RedisServiceDetails{}, err
				}

				orgsCache[org.Guid] = org
			}

			serviceDetails = append(serviceDetails, RedisServiceDetails{
				ServiceInstance: instance,
				Space:           space,
				Org:             org,
			})
		}
	}

	return serviceDetails, nil
}

// The AWS API documentation says that
// CacheClusterId is the user-supplied name
// of the cache cluster. However, in reality,
// it returns that value, plus some extra
// information.
//
// It is in the format {user-supplied}-{n}[-{m}].
// Typically, our cache cluster names have the
// format "cf-{FNV hash of guid}", but that's
// not guaranteed, so this method only strips
// the last one/two parts.
func cleanClusterId(cluster *awsec.CacheCluster) string {
	clusteredRegex := regexp.MustCompile(ClusteredIdPattern)
	unclusteredRegex := regexp.MustCompile(NonClusteredIdPattern)

	strBytes := []byte(aws.StringValue(cluster.CacheClusterId))

	parts := strings.Split(
		aws.StringValue(cluster.CacheClusterId),
		"-",
	)

	var topIndex int
	if clusteredRegex.Match(strBytes) {
		topIndex = len(parts) - 2 // Minus two because we want to stop before the second-to-last part
		return strings.Join(parts[:topIndex], "-")

	} else if unclusteredRegex.Match(strBytes) {
		topIndex = len(parts) - 1 // Minus one because we want to stop before the last part
		return strings.Join(parts[:topIndex], "-")

	} else {
		// Return the original value if it doesn't match either pattern
		return aws.StringValue(cluster.CacheClusterId)
	}
}

func iterateCacheParameterGroups(client elasticacheiface.ElastiCacheAPI, fn func(*awsec.CacheParameterGroup) error) error {
	errs := []error{}
	err := client.DescribeCacheParameterGroupsPages(
		&awsec.DescribeCacheParameterGroupsInput{},
		func(page *awsec.DescribeCacheParameterGroupsOutput, lastPage bool) bool {
			for _, cacheParameterGroup := range page.CacheParameterGroups {
				err := fn(cacheParameterGroup)
				if err != nil {
					errs = append(errs, err)
				}
			}
			return true
		},
	)
	if err != nil {
		return err
	}
	if len(errs) > 0 {
		return errs[0]
	}
	return nil
}

func iterateCacheClusterPages(client elasticacheiface.ElastiCacheAPI, fn func(cluster *awsec.CacheCluster) error) error {
	errs := []error{}
	err := client.DescribeCacheClustersPages(
		&awsec.DescribeCacheClustersInput{},
		func(page *awsec.DescribeCacheClustersOutput, lastPage bool) bool {
			for _, cacheCluster := range page.CacheClusters {
				err := fn(cacheCluster)
				if err != nil {
					errs = append(errs, err)
				}
			}
			return true
		},
	)
	if err != nil {
		return err
	}
	if len(errs) > 0 {
		return errs[0]
	}
	return nil
}
<commit_msg>Refactor and decompose Elasticache metric gauge<commit_after>package main

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface"
	"github.com/cloudfoundry-community/go-cfclient"
	"net/url"
	"regexp"
	"strings"
	"time"

	"code.cloudfoundry.org/lager"
	awsec "github.com/aws/aws-sdk-go/service/elasticache"

	"github.com/alphagov/paas-cf/tools/metrics/pkg/elasticache"

	m "github.com/alphagov/paas-cf/tools/metrics/pkg/metrics"
)

const NonClusteredIdPattern = "[a-z0-9]+-\\d+"
const ClusteredIdPattern = "[a-z0-9]+-\\d+-\\d+"

type RedisServiceDetails struct {
	ServiceInstance cfclient.ServiceInstance
	Space           cfclient.Space
	Org             cfclient.Org
}

func ElasticCacheInstancesGauge(
	logger lager.Logger,
	ecs *elasticache.ElasticacheService,
	cfAPI cfclient.CloudFoundryClient,
	clusterIdHashingFunction elasticache.ElasticacheClusterIdHashingFunction,
	interval time.Duration,
) m.MetricReadCloser {
	return m.NewMetricPoller(interval, func(w m.MetricWriter) error {
		lsess := logger.Session("elasticache-gauges")

		metrics, err := cacheParameterMetrics(ecs)
		if err != nil {
			return err
		}

		clusterMetrics, err := cacheClusterMetrics(lsess, ecs, cfAPI, clusterIdHashingFunction)
		if err != nil {
			return err
		}
		metrics = append(metrics, clusterMetrics...)

		return w.WriteMetrics(metrics)
	})
}

func cacheParameterMetrics(ecs *elasticache.ElasticacheService) ([]m.Metric, error) {
	cacheParameterGroupCount := 0
	err := iterateCacheParameterGroups(
		ecs.Client,
		func(cacheParameterGroup *awsec.CacheParameterGroup) error {
			if !strings.HasPrefix(*cacheParameterGroup.CacheParameterGroupName, "default.") {
				cacheParameterGroupCount++
			}
			return nil
		},
	)
	if err != nil {
		return nil, err
	}

	return []m.Metric{{
		Kind:  m.Gauge,
		Time:  time.Now(),
		Name:  "aws.elasticache.cache_parameter_group.count",
		Value: float64(cacheParameterGroupCount),
		Unit:  "count",
	}}, nil
}

func cacheClusterMetrics(
	logger lager.Logger,
	ecs *elasticache.ElasticacheService,
	cfAPI cfclient.CloudFoundryClient,
	clusterIdHashingFunction elasticache.ElasticacheClusterIdHashingFunction,
) ([]m.Metric, error) {
	redisServiceDetails, err := fetchRedisServiceInstances(cfAPI)
	if err != nil {
		return nil, err
	}

	userSuppliedClusterIdToDetail := map[string]RedisServiceDetails{}
	for _, details := range redisServiceDetails {
		userSuppliedClusterId := clusterIdHashingFunction(details.ServiceInstance.Guid)
		userSuppliedClusterIdToDetail[userSuppliedClusterId] = details
	}

	metrics := []m.Metric{}
	nodeCount := int64(0)
	err = iterateCacheClusterPages(
		ecs.Client,
		func(cacheCluster *awsec.CacheCluster) error {
			nodeCount = nodeCount + *cacheCluster.NumCacheNodes
			elasticacheClusterId := aws.StringValue(cacheCluster.CacheClusterId)
			userSuppliedClusterId := cleanClusterId(cacheCluster)

			details, ok := userSuppliedClusterIdToDetail[userSuppliedClusterId]
			if !ok {
				e := fmt.Errorf("unable to find details of cluster with user supplied cluster id of %s", userSuppliedClusterId)
				logger.Error("details-not-found", e, lager.Data{
					"cluster_id":               elasticacheClusterId,
					"user_supplied_cluster_id": userSuppliedClusterId,
				})
				return e
			}

			metrics = append(metrics, m.Metric{
				Kind:  m.Gauge,
				Time:  time.Now(),
				Name:  "aws.elasticache.cluster.nodes.count",
				Value: float64(*cacheCluster.NumCacheNodes),
				Unit:  "count",
				Tags: m.MetricTags{
					{Label: "cluster_id", Value: userSuppliedClusterId},
					{Label: "elasticache_cluster_id", Value: elasticacheClusterId},
					{Label: "service_instance_guid", Value: details.ServiceInstance.Guid},
					{Label: "space_name", Value: details.Space.Name},
					{Label: "space_guid", Value: details.Space.Guid},
					{Label: "org_name", Value: details.Org.Name},
					{Label: "org_guid", Value: details.Org.Guid},
				},
			})
			return nil
		},
	)
	if err != nil {
		return nil, err
	}

	metrics = append(metrics, m.Metric{
		Kind:  m.Gauge,
		Time:  time.Now(),
		Name:  "aws.elasticache.node.count",
		Value: float64(nodeCount),
		Unit:  "count",
	})
	return metrics, nil
}

func fetchRedisServiceInstances(cfAPI cfclient.CloudFoundryClient) ([]RedisServiceDetails, error) {
	spacesCache := map[string]cfclient.Space{}
	orgsCache := map[string]cfclient.Org{}

	servicesWithRedisLabel, err := cfAPI.ListServicesByQuery(url.Values{
		"q": []string{"label:redis"},
	})

	if err != nil {
		return []RedisServiceDetails{}, err
	}

	if len(servicesWithRedisLabel) == 0 {
		return nil, fmt.Errorf("could not find service with label=redis")
	}

	redisServiceGuid := servicesWithRedisLabel[0].Guid
	redisServicePlans, err := cfAPI.ListServicePlansByQuery(url.Values{
		"q": []string{"service_guid:" + redisServiceGuid},
	})

	if err != nil {
		return []RedisServiceDetails{}, err
	}

	var serviceDetails []RedisServiceDetails
	for _, plan := range redisServicePlans {
		planInstances, err := cfAPI.ListServiceInstancesByQuery(url.Values{
			"q": []string{"service_plan_guid:" + plan.Guid},
		})

		if err != nil {
			return []RedisServiceDetails{}, err
		}

		for _, instance := range planInstances {
			var space cfclient.Space
			if s, ok := spacesCache[instance.SpaceGuid]; ok {
				space = s
			} else {
				space, err = cfAPI.GetSpaceByGuid(instance.SpaceGuid)
				if err != nil {
					return []RedisServiceDetails{}, err
				}

				spacesCache[space.Guid] = space
			}

			var org cfclient.Org
			if o, ok := orgsCache[space.OrganizationGuid]; ok {
				org = o
			} else {
				org, err = cfAPI.GetOrgByGuid(space.OrganizationGuid)
				if err != nil {
					return []RedisServiceDetails{}, err
				}

				orgsCache[org.Guid] = org
			}

			serviceDetails = append(serviceDetails, RedisServiceDetails{
				ServiceInstance: instance,
				Space:           space,
				Org:             org,
			})
		}
	}

	return serviceDetails, nil
}

// The AWS API documentation says that
// CacheClusterId is the user-supplied name
// of the cache cluster. However, in reality,
// it returns that value, plus some extra
// information.
//
// It is in the format {user-supplied}-{n}[-{m}].
// Typically, our cache cluster names have the
// format "cf-{FNV hash of guid}", but that's
// not guaranteed, so this method only strips
// the last one/two parts.
func cleanClusterId(cluster *awsec.CacheCluster) string {
	clusteredRegex := regexp.MustCompile(ClusteredIdPattern)
	unclusteredRegex := regexp.MustCompile(NonClusteredIdPattern)

	strBytes := []byte(aws.StringValue(cluster.CacheClusterId))

	parts := strings.Split(
		aws.StringValue(cluster.CacheClusterId),
		"-",
	)

	var topIndex int
	if clusteredRegex.Match(strBytes) {
		topIndex = len(parts) - 2 // Minus two because we want to stop before the second-to-last part
		return strings.Join(parts[:topIndex], "-")

	} else if unclusteredRegex.Match(strBytes) {
		topIndex = len(parts) - 1 // Minus one because we want to stop before the last part
		return strings.Join(parts[:topIndex], "-")

	} else {
		// Return the original value if it doesn't match either pattern
		return aws.StringValue(cluster.CacheClusterId)
	}
}

func iterateCacheParameterGroups(client elasticacheiface.ElastiCacheAPI, fn func(*awsec.CacheParameterGroup) error) error {
	errs := []error{}
	err := client.DescribeCacheParameterGroupsPages(
		&awsec.DescribeCacheParameterGroupsInput{},
		func(page *awsec.DescribeCacheParameterGroupsOutput, lastPage bool) bool {
			for _, cacheParameterGroup := range page.CacheParameterGroups {
				err := fn(cacheParameterGroup)
				if err != nil {
					errs = append(errs, err)
				}
			}
			return true
		},
	)
	if err != nil {
		return err
	}
	if len(errs) > 0 {
		return errs[0]
	}
	return nil
}

func iterateCacheClusterPages(client elasticacheiface.ElastiCacheAPI, fn func(cluster *awsec.CacheCluster) error) error {
	errs := []error{}
	err := client.DescribeCacheClustersPages(
		&awsec.DescribeCacheClustersInput{},
		func(page *awsec.DescribeCacheClustersOutput, lastPage bool) bool {
			for _, cacheCluster := range page.CacheClusters {
				err := fn(cacheCluster)
				if err != nil {
					errs = append(errs, err)
				}
			}
			return true
		},
	)
	if err != nil {
		return err
	}
	if len(errs) > 0 {
		return errs[0]
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
//
// 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 server

import (
	"encoding/json"
	"fmt"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet"
	"github.com/osrg/gobgp/table"
	"gopkg.in/tomb.v2"
	"net"
)

type Peer struct {
	t              tomb.Tomb
	globalConfig   config.GlobalType
	peerConfig     config.NeighborType
	acceptedConnCh chan *net.TCPConn
	incoming       chan *bgp.BGPMessage
	outgoing       chan *bgp.BGPMessage
	inEventCh      chan *message
	outEventCh     chan *message
	fsm            *FSM
	adjRib         *table.AdjRib
	// peer and rib are always not one-to-one so should not be
	// here but it's the simplest and works our first target.
	rib *table.TableManager
}

func NewPeer(g config.GlobalType, peer config.NeighborType, outEventCh chan *message) *Peer {
	p := &Peer{
		globalConfig:   g,
		peerConfig:     peer,
		acceptedConnCh: make(chan *net.TCPConn),
		incoming:       make(chan *bgp.BGPMessage, 4096),
		outgoing:       make(chan *bgp.BGPMessage, 4096),
		inEventCh:      make(chan *message, 4096),
		outEventCh:     outEventCh,
	}
	p.fsm = NewFSM(&g, &peer, p.acceptedConnCh, p.incoming, p.outgoing)
	p.adjRib = table.NewAdjRib()
	p.rib = table.NewTableManager()
	p.t.Go(p.loop)
	return p
}

func (peer *Peer) handleBGPmessage(m *bgp.BGPMessage) {
	j, _ := json.Marshal(m)
	fmt.Println(string(j))
	// TODO: update state here

	if m.Header.Type != bgp.BGP_MSG_UPDATE {
		return
	}

	msg := table.NewProcessMessage(m, peer.fsm.peerInfo)
	pathList := msg.ToPathList()
	if len(pathList) == 0 {
		return
	}

	peer.adjRib.UpdateIn(pathList)

	peer.sendToHub("", PEER_MSG_PATH, pathList)
}

func (peer *Peer) handlePeermessage(m *message) {
	switch m.event {
	case PEER_MSG_PATH:
		pList, wList, _ := peer.rib.ProcessPaths(m.data.([]table.Path))
	}
}

// this goroutine handles routing table operations
func (peer *Peer) loop() error {
	for {
		h := NewFSMHandler(peer.fsm)
		sameState := true
		for sameState {
			select {
			case nextState := <-peer.fsm.StateChanged():
				// waits for all goroutines created for the current state
				h.Wait()
				peer.fsm.StateChange(nextState)
				sameState = false
			case <-peer.t.Dying():
				close(peer.acceptedConnCh)
				h.Stop()
				close(peer.incoming)
				close(peer.outgoing)
				return nil
			case m := <-peer.incoming:
				if m == nil {
					continue
				}
				peer.handleBGPmessage(m)
			case m := <-peer.inEventCh:
				peer.handlePeermessage(m)
			}
		}
	}
}

func (peer *Peer) Stop() error {
	peer.t.Kill(nil)
	return peer.t.Wait()
}

func (peer *Peer) PassConn(conn *net.TCPConn) {
	peer.acceptedConnCh <- conn
}

func (peer *Peer) SendMessage(msg *message) {
	peer.inEventCh <- msg
}

func (peer *Peer) sendToHub(destination string, event int, data interface{}) {
	peer.outEventCh <- &message{
		src:   peer.peerConfig.NeighborAddress.String(),
		dst:   destination,
		event: event,
		data:  data,
	}
}
<commit_msg>peer: fix the previous commit<commit_after>// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
//
// 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 server

import (
	"encoding/json"
	"fmt"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet"
	"github.com/osrg/gobgp/table"
	"gopkg.in/tomb.v2"
	"net"
)

type Peer struct {
	t              tomb.Tomb
	globalConfig   config.GlobalType
	peerConfig     config.NeighborType
	acceptedConnCh chan *net.TCPConn
	incoming       chan *bgp.BGPMessage
	outgoing       chan *bgp.BGPMessage
	inEventCh      chan *message
	outEventCh     chan *message
	fsm            *FSM
	adjRib         *table.AdjRib
	// peer and rib are always not one-to-one so should not be
	// here but it's the simplest and works our first target.
	rib *table.TableManager
}

func NewPeer(g config.GlobalType, peer config.NeighborType, outEventCh chan *message) *Peer {
	p := &Peer{
		globalConfig:   g,
		peerConfig:     peer,
		acceptedConnCh: make(chan *net.TCPConn),
		incoming:       make(chan *bgp.BGPMessage, 4096),
		outgoing:       make(chan *bgp.BGPMessage, 4096),
		inEventCh:      make(chan *message, 4096),
		outEventCh:     outEventCh,
	}
	p.fsm = NewFSM(&g, &peer, p.acceptedConnCh, p.incoming, p.outgoing)
	p.adjRib = table.NewAdjRib()
	p.rib = table.NewTableManager()
	p.t.Go(p.loop)
	return p
}

func (peer *Peer) handleBGPmessage(m *bgp.BGPMessage) {
	j, _ := json.Marshal(m)
	fmt.Println(string(j))
	// TODO: update state here

	if m.Header.Type != bgp.BGP_MSG_UPDATE {
		return
	}

	msg := table.NewProcessMessage(m, peer.fsm.peerInfo)
	pathList := msg.ToPathList()
	if len(pathList) == 0 {
		return
	}

	peer.adjRib.UpdateIn(pathList)

	peer.sendToHub("", PEER_MSG_PATH, pathList)
}

func (peer *Peer) handlePeermessage(m *message) {
	switch m.event {
	case PEER_MSG_PATH:
		pList, wList, _ := peer.rib.ProcessPaths(m.data.([]table.Path))
		fmt.Println(pList, wList)
	}
}

// this goroutine handles routing table operations
func (peer *Peer) loop() error {
	for {
		h := NewFSMHandler(peer.fsm)
		sameState := true
		for sameState {
			select {
			case nextState := <-peer.fsm.StateChanged():
				// waits for all goroutines created for the current state
				h.Wait()
				peer.fsm.StateChange(nextState)
				sameState = false
			case <-peer.t.Dying():
				close(peer.acceptedConnCh)
				h.Stop()
				close(peer.incoming)
				close(peer.outgoing)
				return nil
			case m := <-peer.incoming:
				if m == nil {
					continue
				}
				peer.handleBGPmessage(m)
			case m := <-peer.inEventCh:
				peer.handlePeermessage(m)
			}
		}
	}
}

func (peer *Peer) Stop() error {
	peer.t.Kill(nil)
	return peer.t.Wait()
}

func (peer *Peer) PassConn(conn *net.TCPConn) {
	peer.acceptedConnCh <- conn
}

func (peer *Peer) SendMessage(msg *message) {
	peer.inEventCh <- msg
}

func (peer *Peer) sendToHub(destination string, event int, data interface{}) {
	peer.outEventCh <- &message{
		src:   peer.peerConfig.NeighborAddress.String(),
		dst:   destination,
		event: event,
		data:  data,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"github.com/google/go-cmp/cmp"
)

func TestAllPattern_AllPattern(t *testing.T) {
	type want struct {
		StatusCode int
		Method     string
		Path       string
		Resp       *AllPatternMessage
	}
	tests := []struct {
		name    string
		reqFunc func() (*http.Request, error)
		cb      func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error)
		wantErr bool
		want    *want
	}{
		{
			name: "GET method and Content-Type JSON",
			reqFunc: func() (*http.Request, error) {
				req := httptest.NewRequest(http.MethodGet, "/all/pattern", nil)
				req.Header.Set("Content-Type", "application/json")
				return req, nil
			},
			cb: func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error) {
				t.Log(err)
			},
			wantErr: false,
			want: &want{
				StatusCode: http.StatusOK,
				Method:     http.MethodGet,
				Path:       "/all/pattern",
				Resp:       &AllPatternMessage{},
			},
		},
		{
			name: "GET method and Content-Type Protobuf",
			reqFunc: func() (*http.Request, error) {
				req := httptest.NewRequest(http.MethodGet, "/all/pattern", nil)
				req.Header.Set("Content-Type", "application/protobuf")
				return req, nil
			},
			cb:      nil,
			wantErr: false,
			want: &want{
				StatusCode: http.StatusOK,
				Method:     http.MethodGet,
				Path:       "/all/pattern",
				Resp: &AllPatternMessage{
					RepeatedDouble: make([]float64, 0),
				},
			},
		},
	}

	handler := NewAllPatternHTTPConverter(&AllPattern{})

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			req, err := tt.reqFunc()
			if err != nil {
				t.Fatal(err)
			}

			rec := httptest.NewRecorder()
			method, path, h := handler.AllPatternHTTPRule(tt.cb)
			h.ServeHTTP(rec, req)

			var resp *AllPatternMessage
			if !tt.wantErr {
				resp = &AllPatternMessage{}
				switch req.Header.Get("Content-Type") {
				case "application/protobuf":
					if err := proto.Unmarshal(rec.Body.Bytes(), resp); err != nil {
						t.Fatal(err)
					}
				case "application/json":
					if err := jsonpb.Unmarshal(rec.Body, resp); err != nil {
						t.Fatal(err)
					}
				default:
				}
			}

			actual := &want{
				StatusCode: rec.Code,
				Method:     method,
				Path:       path,
				Resp:       resp,
			}

			if diff := cmp.Diff(actual, tt.want); diff != "" {
				t.Errorf("%s", diff)
			}
		})
	}
}
<commit_msg>Filled empty slice at JSON body test<commit_after>package main

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"github.com/google/go-cmp/cmp"
)

func TestAllPattern_AllPattern(t *testing.T) {
	type want struct {
		StatusCode int
		Method     string
		Path       string
		Resp       *AllPatternMessage
	}
	tests := []struct {
		name    string
		reqFunc func() (*http.Request, error)
		cb      func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error)
		wantErr bool
		want    *want
	}{
		{
			name: "GET method, Content-Type JSON and Empty body",
			reqFunc: func() (*http.Request, error) {
				req := httptest.NewRequest(http.MethodGet, "/all/pattern", nil)
				req.Header.Set("Content-Type", "application/json")
				return req, nil
			},
			cb: func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error) {
				t.Log(err)
			},
			wantErr: false,
			want: &want{
				StatusCode: http.StatusOK,
				Method:     http.MethodGet,
				Path:       "/all/pattern",
				Resp: &AllPatternMessage{
					// Empty array
					RepeatedDouble:   make([]float64, 0),
					RepeatedFloat:    make([]float32, 0),
					RepeatedInt32:    make([]int32, 0),
					RepeatedInt64:    make([]int64, 0),
					RepeatedUint32:   make([]uint32, 0),
					RepeatedUint64:   make([]uint64, 0),
					RepeatedFixed32:  make([]uint32, 0),
					RepeatedFixed64:  make([]uint64, 0),
					RepeatedSfixed32: make([]int32, 0),
					RepeatedSfixed64: make([]int64, 0),
					RepeatedBool:     make([]bool, 0),
					RepeatedString:   make([]string, 0),
					RepeatedBytes:    make([][]byte, 0),
				},
			},
		},
		{
			name: "GET method, Content-Type Protobuf and Empty body",
			reqFunc: func() (*http.Request, error) {
				req := httptest.NewRequest(http.MethodGet, "/all/pattern", nil)
				req.Header.Set("Content-Type", "application/protobuf")
				return req, nil
			},
			cb:      nil,
			wantErr: false,
			want: &want{
				StatusCode: http.StatusOK,
				Method:     http.MethodGet,
				Path:       "/all/pattern",
				Resp:       &AllPatternMessage{},
			},
		},
	}

	handler := NewAllPatternHTTPConverter(&AllPattern{})

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			req, err := tt.reqFunc()
			if err != nil {
				t.Fatal(err)
			}

			rec := httptest.NewRecorder()
			method, path, h := handler.AllPatternHTTPRule(tt.cb)
			h.ServeHTTP(rec, req)

			var resp *AllPatternMessage
			if !tt.wantErr {
				resp = &AllPatternMessage{}
				switch req.Header.Get("Content-Type") {
				case "application/protobuf":
					if err := proto.Unmarshal(rec.Body.Bytes(), resp); err != nil {
						t.Fatal(err)
					}
				case "application/json":
					if err := jsonpb.Unmarshal(rec.Body, resp); err != nil {
						t.Fatal(err)
					}
				default:
				}
			}

			actual := &want{
				StatusCode: rec.Code,
				Method:     method,
				Path:       path,
				Resp:       resp,
			}

			if diff := cmp.Diff(actual, tt.want); diff != "" {
				t.Errorf("%s", diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>package provider

import (
	"github.com/erikvanbrakel/terraform-provider-sumologic/go-sumologic"
	"github.com/hashicorp/terraform/helper/schema"
	"strconv"
	"log"
)

func resourceSumologicPollingSource() *schema.Resource {
	return &schema.Resource{
		Create: resourceSumologicPollingSourceCreate,
		Read:   resourceSumologicPollingSourceRead,
		Update: resourceSumologicPollingSourceUpdate,
		Delete: resourceSumologicPollingSourceDelete,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},
			"category": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},
			"content_type": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"scan_interval": {
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: false,
			},
			"paused": {
				Type:     schema.TypeBool,
				Required: true,
				ForceNew: false,
			},
			"collector_id": {
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},
			"authentication": {
				Type:     schema.TypeList,
				Required: true,
				ForceNew: true,
				MinItems: 1,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"access_key": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
						"secret_key": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
					},
				},
			},
			"path": {
				Type:     schema.TypeList,
				Required: true,
				ForceNew: true,
				MinItems: 1,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"bucket_name": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
						"path_expression": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
					},
				},
			},
		},
	}
}

func resourceSumologicPollingSourceCreate(d *schema.ResourceData, meta interface{}) error {

	c := meta.(*sumologic.SumologicClient)

	sourceId, err := c.CreatePollingSource(
		d.Get("name").(string),
		d.Get("content_type").(string),
		d.Get("category").(string),
		d.Get("scan_interval").(int),
		d.Get("paused").(bool),
		d.Get("collector_id").(int),
		getAuthentication(d),
		getPathSettings(d),
	)

	if err != nil {
		return err
	}

	id := strconv.Itoa(sourceId)

	d.SetId(id)

	return resourceSumologicPollingSourceRead(d, meta)
}

func resourceSumologicPollingSourceRead(d *schema.ResourceData, meta interface{}) error {

	c := meta.(*sumologic.SumologicClient)

	id, err := strconv.Atoi(d.Id())
	collector_id := d.Get("collector_id").(int)

	source, err := c.GetPollingSource(collector_id, id)

	if err != nil {
		return err
	}

	pollingResources := source.ThirdPartyRef.Resources
	path := getThirdyPartyPathAttributes(pollingResources)

	if err := d.Set("path", path); err != nil {
		return err
	}

	d.Set("name", source.Name)
	d.Set("content_type", source.ContentType)
	d.Set("category", source.Category)
	d.Set("scan_interval", source.ScanInterval)
	d.Set("paused", source.Paused)

	return nil
}

func resourceSumologicPollingSourceDelete(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*sumologic.SumologicClient)

	id, _ := strconv.Atoi(d.Id())
	collector_id, _ := d.Get("collector_id").(int)

	return c.DestroySource(id, collector_id)
}

func resourceSumologicPollingSourceUpdate(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*sumologic.SumologicClient)

	source := resourceToPollingSource(d)

	err := c.UpdatePollingSource(source, d.Get("collector_id").(int))

	if err != nil {
		return err
	}

	return resourceSumologicPollingSourceRead(d, meta)
}

func resourceToPollingSource(d *schema.ResourceData) sumologic.PollingSource {

	id, _ := strconv.Atoi(d.Id())
	source := sumologic.PollingSource{}
	pollingResource := sumologic.PollingResource{}

	source.Id = id
	source.Type = "Polling"
	source.Category = d.Get("category").(string)
	source.Paused = d.Get("paused").(bool)
	source.Name = d.Get("name").(string)
	source.ScanInterval = d.Get("scan_interval").(int)
	source.ContentType = d.Get("content_type").(string)

	pollingResource.ServiceType    = "AwsS3AuditBucket"
	pollingResource.Authentication = getAuthentication(d)
	pollingResource.Path           = getPathSettings(d)

	source.ThirdPartyRef.Resources = append(source.ThirdPartyRef.Resources, pollingResource)

	return source
}

func getThirdyPartyPathAttributes(pollingResource []sumologic.PollingResource) []map[string]interface{} {

	var s []map[string]interface{}
	for _, t := range pollingResource {
		mapping := map[string]interface{}{
			"bucket_name":        t.Path.BucketName,
			"path_expression":    t.Path.PathExpression,
		}
		s = append(s, mapping)
	}

	return s
}

func getAuthentication(d *schema.ResourceData) sumologic.PollingAuthentication {

	auths := d.Get("authentication").([]interface{})
	authSettings := sumologic.PollingAuthentication{}

	if len(auths) > 0 {
		auth := auths[0].(map[string]interface{})
		authSettings.Type = "S3BucketAuthentication"
		authSettings.AwsId = auth["access_key"].(string)
		authSettings.AwsKey = auth["secret_key"].(string)
	}

	return authSettings
}

func getPathSettings(d *schema.ResourceData) sumologic.PollingPath {
	pathSettings := sumologic.PollingPath{}
	paths := d.Get("path").([]interface{})

	if len(paths) > 0 {
		path := paths[0].(map[string]interface{})
		pathSettings.Type = "S3BucketPathExpression"
		pathSettings.BucketName = path["bucket_name"].(string)
		pathSettings.PathExpression = path["path_expression"].(string)
	}

	return pathSettings
}
<commit_msg>removed unused import<commit_after>package provider

import (
	"github.com/erikvanbrakel/terraform-provider-sumologic/go-sumologic"
	"github.com/hashicorp/terraform/helper/schema"
	"strconv"
)

func resourceSumologicPollingSource() *schema.Resource {
	return &schema.Resource{
		Create: resourceSumologicPollingSourceCreate,
		Read:   resourceSumologicPollingSourceRead,
		Update: resourceSumologicPollingSourceUpdate,
		Delete: resourceSumologicPollingSourceDelete,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},
			"category": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},
			"content_type": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"scan_interval": {
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: false,
			},
			"paused": {
				Type:     schema.TypeBool,
				Required: true,
				ForceNew: false,
			},
			"collector_id": {
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},
			"authentication": {
				Type:     schema.TypeList,
				Required: true,
				ForceNew: true,
				MinItems: 1,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"access_key": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
						"secret_key": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
					},
				},
			},
			"path": {
				Type:     schema.TypeList,
				Required: true,
				ForceNew: true,
				MinItems: 1,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"bucket_name": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
						"path_expression": {
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
					},
				},
			},
		},
	}
}

func resourceSumologicPollingSourceCreate(d *schema.ResourceData, meta interface{}) error {

	c := meta.(*sumologic.SumologicClient)

	sourceId, err := c.CreatePollingSource(
		d.Get("name").(string),
		d.Get("content_type").(string),
		d.Get("category").(string),
		d.Get("scan_interval").(int),
		d.Get("paused").(bool),
		d.Get("collector_id").(int),
		getAuthentication(d),
		getPathSettings(d),
	)

	if err != nil {
		return err
	}

	id := strconv.Itoa(sourceId)

	d.SetId(id)

	return resourceSumologicPollingSourceRead(d, meta)
}

func resourceSumologicPollingSourceRead(d *schema.ResourceData, meta interface{}) error {

	c := meta.(*sumologic.SumologicClient)

	id, err := strconv.Atoi(d.Id())
	collector_id := d.Get("collector_id").(int)

	source, err := c.GetPollingSource(collector_id, id)

	if err != nil {
		return err
	}

	pollingResources := source.ThirdPartyRef.Resources
	path := getThirdyPartyPathAttributes(pollingResources)

	if err := d.Set("path", path); err != nil {
		return err
	}

	d.Set("name", source.Name)
	d.Set("content_type", source.ContentType)
	d.Set("category", source.Category)
	d.Set("scan_interval", source.ScanInterval)
	d.Set("paused", source.Paused)

	return nil
}

func resourceSumologicPollingSourceDelete(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*sumologic.SumologicClient)

	id, _ := strconv.Atoi(d.Id())
	collector_id, _ := d.Get("collector_id").(int)

	return c.DestroySource(id, collector_id)
}

func resourceSumologicPollingSourceUpdate(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*sumologic.SumologicClient)

	source := resourceToPollingSource(d)

	err := c.UpdatePollingSource(source, d.Get("collector_id").(int))

	if err != nil {
		return err
	}

	return resourceSumologicPollingSourceRead(d, meta)
}

func resourceToPollingSource(d *schema.ResourceData) sumologic.PollingSource {

	id, _ := strconv.Atoi(d.Id())
	source := sumologic.PollingSource{}
	pollingResource := sumologic.PollingResource{}

	source.Id = id
	source.Type = "Polling"
	source.Category = d.Get("category").(string)
	source.Paused = d.Get("paused").(bool)
	source.Name = d.Get("name").(string)
	source.ScanInterval = d.Get("scan_interval").(int)
	source.ContentType = d.Get("content_type").(string)

	pollingResource.ServiceType    = "AwsS3AuditBucket"
	pollingResource.Authentication = getAuthentication(d)
	pollingResource.Path           = getPathSettings(d)

	source.ThirdPartyRef.Resources = append(source.ThirdPartyRef.Resources, pollingResource)

	return source
}

func getThirdyPartyPathAttributes(pollingResource []sumologic.PollingResource) []map[string]interface{} {

	var s []map[string]interface{}
	for _, t := range pollingResource {
		mapping := map[string]interface{}{
			"bucket_name":        t.Path.BucketName,
			"path_expression":    t.Path.PathExpression,
		}
		s = append(s, mapping)
	}

	return s
}

func getAuthentication(d *schema.ResourceData) sumologic.PollingAuthentication {

	auths := d.Get("authentication").([]interface{})
	authSettings := sumologic.PollingAuthentication{}

	if len(auths) > 0 {
		auth := auths[0].(map[string]interface{})
		authSettings.Type = "S3BucketAuthentication"
		authSettings.AwsId = auth["access_key"].(string)
		authSettings.AwsKey = auth["secret_key"].(string)
	}

	return authSettings
}

func getPathSettings(d *schema.ResourceData) sumologic.PollingPath {
	pathSettings := sumologic.PollingPath{}
	paths := d.Get("path").([]interface{})

	if len(paths) > 0 {
		path := paths[0].(map[string]interface{})
		pathSettings.Type = "S3BucketPathExpression"
		pathSettings.BucketName = path["bucket_name"].(string)
		pathSettings.PathExpression = path["path_expression"].(string)
	}

	return pathSettings
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math"

	"github.com/jzacsh/netwtcpip-cmp405/parseip4"
)

var partTwoHosts = []parseip4.Addr{
	{IP: parseip4.NewAddr(9, 201, 195, 84), Mask: parseip4.NewAddr(255, 255, 240, 0)},
	{IP: parseip4.NewAddr(128, 10, 189, 215), Mask: parseip4.NewAddr(255, 255, 248, 0)},
	{IP: parseip4.NewAddr(135, 21, 243, 82), Mask: parseip4.NewAddr(255, 255, 224, 0)},
	{IP: parseip4.NewAddr(75, 149, 205, 61), Mask: parseip4.NewAddr(255, 255, 192, 0)},
	{IP: parseip4.NewAddr(7, 105, 198, 111), Mask: parseip4.NewAddr(255, 255, 252, 0)},

	// TODO(zacsh) remove this sample from the last slide
	{IP: parseip4.NewAddr(128, 10, 211, 78), Mask: parseip4.NewAddr(255, 255, 240, 0)},
}

type subnetRequisites struct {
	ClassfulContext parseip4.OctsList
	MaxSubnets      uint
	SubnetIndex     parseip4.Octets
	HostIndex       parseip4.Octets
}

type OptimalSubnet struct {
	MinSubnetBits     uint
	MaxHostsPerSubnet parseip4.Octets
	Address           parseip4.Addr
}

var partOneGivens = []subnetRequisites{
	{parseip4.OctsList{128, 10, 0, 0}, 55, 51, 121},
	{parseip4.OctsList{128, 10, 0, 0}, 55, 42, 867},
	{parseip4.OctsList{128, 10, 0, 0}, 121, 115, 246},
	{parseip4.OctsList{128, 10, 0, 0}, 121, 97, 443},
	{parseip4.OctsList{128, 10, 0, 0}, 26, 19, 237},
	{parseip4.OctsList{128, 10, 0, 0}, 26, 25, 1397},
	{parseip4.OctsList{128, 10, 0, 0}, 261, 227, 86},
	{parseip4.OctsList{128, 10, 0, 0}, 261, 259, 49},
	{parseip4.OctsList{128, 10, 0, 0}, 529, 519, 33},
	{parseip4.OctsList{128, 10, 0, 0}, 529, 510, 59},
}

func (s *subnetRequisites) String() string {
	return fmt.Sprintf(
		"max subnets: %d, subnet index: %d, host index: %d",
		s.MaxSubnets, s.SubnetIndex, s.HostIndex)
}

func maxIntWithBits(nbits uint) uint32 {
	// 1 because 2^N bits only gets 2^N-1 if all 1s. +1 more because all 1s is
	// reserved for broadcast.
	const gap float64 = 2

	maxInt := math.Pow(2, float64(nbits))
	if maxInt < gap {
		// we want to avoid underflows, so stick to the point of the API and return
		// effectively zero
		return 0
	}

	return uint32(maxInt - gap)
}

func (s *subnetRequisites) FindSolution() OptimalSubnet {
	opt := OptimalSubnet{}

	// Brute force solve for Ceil(log2(s.MaxSubnets))
	for {
		if maxIntWithBits(opt.MinSubnetBits) >= uint32(s.MaxSubnets) {
			break
		}
		opt.MinSubnetBits++
	}

	opt.MaxHostsPerSubnet = parseip4.Octets(maxIntWithBits(32 - opt.MinSubnetBits))

	// TODO opt.Address.Mask
	mask := parseip4.Octets(0xFFFFFFFF)
	mask <<= (32 - opt.MinSubnetBits)
	opt.Address.Mask = mask.List()

	// TODO opt.Address.IP
	ip := parseip4.Octets(0xFFFFFFFF)
	opt.Address.IP = ip.List()

	return opt
}

func main() {
	fmt.Printf("part 1: analyzing %d hosts ...\n", len(partOneGivens))
	for _, req := range partOneGivens {
		sol := req.FindSolution()
		fmt.Printf(
			"  given: %s\n\tmin # of subnet bits: %d\n\tmax # hosts per subnet: %d\n\taddress: %s\n",
			req.String(),
			sol.MinSubnetBits,
			sol.MaxHostsPerSubnet,
			sol.Address.String())
	}

	fmt.Printf("\n\npart 2: analyzing %d hosts ...\n", len(partTwoHosts))
	for _, addr := range partTwoHosts {
		classMask, _, klass := parseip4.Classful(addr.IP)

		fmt.Printf(
			"  network: %v (class %s masked)\n\t%v\n\tnetwork id:\t%d\n\t subnet id:\t%d\n\t   host id:\t%d\n",
			(addr.IP.Pack() & classMask.Pack()).List(), klass,
			addr.String(),
			addr.NetworkIndex(),
			addr.SubnetIndex(),
			addr.HostIndex())
	}
}
<commit_msg>hw4(part1.3) finish calculating ip address<commit_after>package main

import (
	"fmt"
	"math"

	"github.com/jzacsh/netwtcpip-cmp405/parseip4"
)

var partTwoHosts = []parseip4.Addr{
	{IP: parseip4.NewAddr(9, 201, 195, 84), Mask: parseip4.NewAddr(255, 255, 240, 0)},
	{IP: parseip4.NewAddr(128, 10, 189, 215), Mask: parseip4.NewAddr(255, 255, 248, 0)},
	{IP: parseip4.NewAddr(135, 21, 243, 82), Mask: parseip4.NewAddr(255, 255, 224, 0)},
	{IP: parseip4.NewAddr(75, 149, 205, 61), Mask: parseip4.NewAddr(255, 255, 192, 0)},
	{IP: parseip4.NewAddr(7, 105, 198, 111), Mask: parseip4.NewAddr(255, 255, 252, 0)},

	// TODO(zacsh) remove this sample from the last slide
	{IP: parseip4.NewAddr(128, 10, 211, 78), Mask: parseip4.NewAddr(255, 255, 240, 0)},
}

type subnetRequisites struct {
	ClassfulContext parseip4.OctsList
	MaxSubnets      uint
	SubnetIndex     parseip4.Octets
	HostIndex       parseip4.Octets
}

type OptimalSubnet struct {
	MinSubnetBits     uint
	MaxHostsPerSubnet parseip4.Octets
	Address           parseip4.Addr
}

var partOneGivens = []subnetRequisites{
	{parseip4.OctsList{128, 10, 0, 0}, 55, 51, 121},
	{parseip4.OctsList{128, 10, 0, 0}, 55, 42, 867},
	{parseip4.OctsList{128, 10, 0, 0}, 121, 115, 246},
	{parseip4.OctsList{128, 10, 0, 0}, 121, 97, 443},
	{parseip4.OctsList{128, 10, 0, 0}, 26, 19, 237},
	{parseip4.OctsList{128, 10, 0, 0}, 26, 25, 1397},
	{parseip4.OctsList{128, 10, 0, 0}, 261, 227, 86},
	{parseip4.OctsList{128, 10, 0, 0}, 261, 259, 49},
	{parseip4.OctsList{128, 10, 0, 0}, 529, 519, 33},
	{parseip4.OctsList{128, 10, 0, 0}, 529, 510, 59},
}

func (s *subnetRequisites) String() string {
	return fmt.Sprintf(
		"max subnets: %d, subnet index: %d, host index: %d",
		s.MaxSubnets, s.SubnetIndex, s.HostIndex)
}

func maxIntWithBits(nbits uint) uint32 {
	// 1 because 2^N bits only gets 2^N-1 if all 1s. +1 more because all 1s is
	// reserved for broadcast.
	const gap float64 = 2

	maxInt := math.Pow(2, float64(nbits))
	if maxInt < gap {
		// we want to avoid underflows, so stick to the point of the API and return
		// effectively zero
		return 0
	}

	return uint32(maxInt - gap)
}

func (s *subnetRequisites) FindSolution() OptimalSubnet {
	opt := OptimalSubnet{}

	// Brute force solve for Ceil(log2(s.MaxSubnets))
	for {
		if maxIntWithBits(opt.MinSubnetBits) >= uint32(s.MaxSubnets) {
			break
		}
		opt.MinSubnetBits++
	}

	opt.MaxHostsPerSubnet = parseip4.Octets(maxIntWithBits(32 - opt.MinSubnetBits))

	mask := parseip4.Octets(0xFFFFFFFF)
	mask <<= (32 - opt.MinSubnetBits)
	opt.Address.Mask = mask.List()

	_, classCidrOffset, _ := parseip4.Classful(s.ClassfulContext)
	subnetBitCount := parseip4.CountBitSize(s.SubnetIndex)
	hostBitAddrSpace := 32 - classCidrOffset - subnetBitCount

	ip := s.ClassfulContext.Pack() |
		parseip4.Octets(s.SubnetIndex<<hostBitAddrSpace) |
		s.HostIndex
	opt.Address.IP = ip.List()

	return opt
}

func main() {
	fmt.Printf("part 1: analyzing %d hosts ...\n", len(partOneGivens))
	for _, req := range partOneGivens {
		sol := req.FindSolution()
		fmt.Printf(
			"  given: %s\n\tmin # of subnet bits: %d\n\tmax # hosts per subnet: %d\n\taddress: %s\n",
			req.String(),
			sol.MinSubnetBits,
			sol.MaxHostsPerSubnet,
			sol.Address.String())
	}

	fmt.Printf("\npart 2: analyzing %d hosts ...\n", len(partTwoHosts))
	for _, addr := range partTwoHosts {
		classMask, _, klass := parseip4.Classful(addr.IP)

		fmt.Printf(
			"  network: %v (class %s masked)\n\t%v\n\tnetwork id:\t%d\n\t subnet id:\t%d\n\t   host id:\t%d\n",
			(addr.IP.Pack() & classMask.Pack()).List(), klass,
			addr.String(),
			addr.NetworkIndex(),
			addr.SubnetIndex(),
			addr.HostIndex())
	}
}
<|endoftext|>


<commit_before>package uploads

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

// A chunk contains a piece of the data to assemble.
type chunk interface {
	Name() string               // Name of the item
	Reader() (io.Reader, error) // Returns a reader to get at the items data
}

// A chunkSupplier supplies a list of chunks. Its useful for implementing
// different ways to get a list of chunks.
type chunkSupplier interface {
	chunks() ([]chunk, error)
}

// dirChunk implements the chunk interface. It provides an item for
// each file in a directory.
type dirChunk struct {
	os.FileInfo
	reader func() (io.Reader, error)
}

// Reader returns a new io.Reader for the given dirChunk file entry.
func (d dirChunk) Reader() (io.Reader, error) {
	return d.reader()
}

// A dirChunkSupplier returns a list of chunks from a directory.
type dirChunkSupplier struct {
	dir string
}

// newDirChunkSupplier creates a new dirChunkSupplier for the given directory path.
func newDirChunkSupplier(dir string) *dirChunkSupplier {
	return &dirChunkSupplier{
		dir: dir,
	}
}

// chunks returns a list of the files in a given directory as a set of chunks. Chunks are
// returned in sorted order (ioutil.ReadDir will return the directory contents in sorted
// order).
func (s *dirChunkSupplier) chunks() ([]chunk, error) {
	finfos, err := ioutil.ReadDir(s.dir)
	if err != nil {
		return nil, err
	}

	var dirChunks []chunk
	for _, finfo := range finfos {
		if !finfo.IsDir() {
			saveFinfo := finfo
			chunk := dirChunk{
				FileInfo: saveFinfo,
				reader: func() (io.Reader, error) {
					return os.Open(filepath.Join(s.dir, saveFinfo.Name()))
				},
			}
			dirChunks = append(dirChunks, chunk)
		}
	}
	return dirChunks, err
}
<commit_msg>Update comment to describe the requirements for the interface.<commit_after>package uploads

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

// A chunk contains a piece of the data to assemble.
type chunk interface {
	Name() string               // Name of the item
	Reader() (io.Reader, error) // Returns a reader to get at the items data
}

// A chunkSupplier supplies a list of chunks. Its useful for implementing
// different ways to get a list of chunks.
type chunkSupplier interface {
	// chunks must return its list of chunks in sorted order.
	chunks() ([]chunk, error)
}

// dirChunk implements the chunk interface. It provides an item for
// each file in a directory.
type dirChunk struct {
	os.FileInfo
	reader func() (io.Reader, error)
}

// Reader returns a new io.Reader for the given dirChunk file entry.
func (d dirChunk) Reader() (io.Reader, error) {
	return d.reader()
}

// A dirChunkSupplier returns a list of chunks from a directory.
type dirChunkSupplier struct {
	dir string
}

// newDirChunkSupplier creates a new dirChunkSupplier for the given directory path.
func newDirChunkSupplier(dir string) *dirChunkSupplier {
	return &dirChunkSupplier{
		dir: dir,
	}
}

// chunks returns a list of the files in a given directory as a set of chunks. Chunks are
// returned in sorted order (ioutil.ReadDir will return the directory contents in sorted
// order).
func (s *dirChunkSupplier) chunks() ([]chunk, error) {
	finfos, err := ioutil.ReadDir(s.dir)
	if err != nil {
		return nil, err
	}

	var dirChunks []chunk
	for _, finfo := range finfos {
		if !finfo.IsDir() {
			saveFinfo := finfo
			chunk := dirChunk{
				FileInfo: saveFinfo,
				reader: func() (io.Reader, error) {
					return os.Open(filepath.Join(s.dir, saveFinfo.Name()))
				},
			}
			dirChunks = append(dirChunks, chunk)
		}
	}
	return dirChunks, err
}
<|endoftext|>


<commit_before>package uploads

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/materials-commons/gohandy/file"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/app/flow"
)

// RequestWriter is the interface used to write a request.
type requestWriter interface {
	write(dir string, req *flow.Request) error
}

// A fileRequestWriter implements writing a request to a file.
type fileRequestWriter struct{}

// Write will write the blocks for a request to the path returned by
// the RequestPath Path call. Write will attempt to create the directory
// path to write to.
func (r *fileRequestWriter) write(dir string, req *flow.Request) error {
	path := filepath.Join(dir, fmt.Sprintf("%d", req.FlowChunkNumber))
	err := r.validateWrite(dir, path, req)
	switch {
	case err == nil:
		return ioutil.WriteFile(path, req.Chunk, 0700)
	case err == app.ErrExists:
		return nil
	default:
		return err
	}
}

// validateWrite determines if a particular chunk can be written.
// If the size of the on disk chunk is smaller than the request
// chunk then that chunk is incomplete and we allow a write to it.
func (r *fileRequestWriter) validateWrite(dir, path string, req *flow.Request) error {
	// Create directory where chunk will be written
	if err := os.MkdirAll(dir, 0700); err != nil {
		return err
	}

	finfo, err := os.Stat(path)
	switch {
	case os.IsNotExist(err):
		return nil
	case err != nil:
		return app.ErrInvalid
	case finfo.Size() < int64(req.FlowChunkSize):
		return nil
	case finfo.Size() == int64(req.FlowChunkSize):
		return app.ErrExists
	default:
		return app.ErrInvalid
	}
}

// blockRequestWriter implements writing requests to a single file. It writes the
// requests in order by creating a sparse file and then seeking to the proper spot
// in the file to write the requests data.
type blockRequestWriter struct{}

// write will write the request to a file located in dir. The file will have
// the name of the flow UploadID(). This method creates a sparse file the
// size of the file to be written and then writes requests in order. Out of
// order chunks are handled by seeking to proper position in the file.
func (r *blockRequestWriter) write(dir string, req *flow.Request) error {
	path := filepath.Join(dir, req.UploadID())
	if err := r.createFile(dir, path, req.FlowTotalSize); err != nil {
		return err
	}
	return r.writeRequest(path, req)
}

// createFile ensures that the path exists. If needed it will create the directory and
// the file. The file is created as a sparse file.
func (r *blockRequestWriter) createFile(dir, path string, size int64) error {
	if !file.Exists(path) {
		if err := os.MkdirAll(dir, 0700); err != nil {
			return err
		}
		return createSparseFile(path, size)
	}
	return nil
}

// createSparseFile creates a new sparse file at path of size.
func createSparseFile(path string, size int64) error {
	if f, err := os.Create(path); err != nil {
		return err
	} else {
		defer f.Close()
		return f.Truncate(size)
	}
}

// writeRequest performs the actual write of the request. It opens the file
// sparse file, seeks to the proper position and then writes the data.
func (r *blockRequestWriter) writeRequest(path string, req *flow.Request) error {
	if f, err := os.OpenFile(path, os.O_WRONLY, 0660); err != nil {
		return err
	} else {
		defer f.Close()

		seekTo := int64((req.FlowChunkNumber - 1) * req.FlowChunkSize)
		if _, err := f.Seek(seekTo, os.SEEK_SET); err != nil {
			app.Log.Critf("Failed seeking to write chunk #%d for %s: %s", req.FlowChunkNumber, req.UploadID(), err)
			return err
		}

		fmt.Println("writing chunk", string(req.Chunk))
		if _, err := f.Write(req.Chunk); err != nil {
			app.Log.Critf("Failed writing chunk #%d for %s: %s", req.FlowChunkNumber, req.UploadID(), err)
			return err
		}
		return nil
	}
}

type mockRequestWriter struct {
	err error
}

func (r *mockRequestWriter) write(dir string, req *flow.Request) error {
	return r.err
}
<commit_msg>Remove Printf.<commit_after>package uploads

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/materials-commons/gohandy/file"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/app/flow"
)

// RequestWriter is the interface used to write a request.
type requestWriter interface {
	write(dir string, req *flow.Request) error
}

// A fileRequestWriter implements writing a request to a file.
type fileRequestWriter struct{}

// Write will write the blocks for a request to the path returned by
// the RequestPath Path call. Write will attempt to create the directory
// path to write to.
func (r *fileRequestWriter) write(dir string, req *flow.Request) error {
	path := filepath.Join(dir, fmt.Sprintf("%d", req.FlowChunkNumber))
	err := r.validateWrite(dir, path, req)
	switch {
	case err == nil:
		return ioutil.WriteFile(path, req.Chunk, 0700)
	case err == app.ErrExists:
		return nil
	default:
		return err
	}
}

// validateWrite determines if a particular chunk can be written.
// If the size of the on disk chunk is smaller than the request
// chunk then that chunk is incomplete and we allow a write to it.
func (r *fileRequestWriter) validateWrite(dir, path string, req *flow.Request) error {
	// Create directory where chunk will be written
	if err := os.MkdirAll(dir, 0700); err != nil {
		return err
	}

	finfo, err := os.Stat(path)
	switch {
	case os.IsNotExist(err):
		return nil
	case err != nil:
		return app.ErrInvalid
	case finfo.Size() < int64(req.FlowChunkSize):
		return nil
	case finfo.Size() == int64(req.FlowChunkSize):
		return app.ErrExists
	default:
		return app.ErrInvalid
	}
}

// blockRequestWriter implements writing requests to a single file. It writes the
// requests in order by creating a sparse file and then seeking to the proper spot
// in the file to write the requests data.
type blockRequestWriter struct{}

// write will write the request to a file located in dir. The file will have
// the name of the flow UploadID(). This method creates a sparse file the
// size of the file to be written and then writes requests in order. Out of
// order chunks are handled by seeking to proper position in the file.
func (r *blockRequestWriter) write(dir string, req *flow.Request) error {
	path := filepath.Join(dir, req.UploadID())
	if err := r.createFile(dir, path, req.FlowTotalSize); err != nil {
		return err
	}
	return r.writeRequest(path, req)
}

// createFile ensures that the path exists. If needed it will create the directory and
// the file. The file is created as a sparse file.
func (r *blockRequestWriter) createFile(dir, path string, size int64) error {
	if !file.Exists(path) {
		if err := os.MkdirAll(dir, 0700); err != nil {
			return err
		}
		return createSparseFile(path, size)
	}
	return nil
}

// createSparseFile creates a new sparse file at path of size.
func createSparseFile(path string, size int64) error {
	if f, err := os.Create(path); err != nil {
		return err
	} else {
		defer f.Close()
		return f.Truncate(size)
	}
}

// writeRequest performs the actual write of the request. It opens the file
// sparse file, seeks to the proper position and then writes the data.
func (r *blockRequestWriter) writeRequest(path string, req *flow.Request) error {
	if f, err := os.OpenFile(path, os.O_WRONLY, 0660); err != nil {
		return err
	} else {
		defer f.Close()

		seekTo := int64((req.FlowChunkNumber - 1) * req.FlowChunkSize)
		if _, err := f.Seek(seekTo, os.SEEK_SET); err != nil {
			app.Log.Critf("Failed seeking to write chunk #%d for %s: %s", req.FlowChunkNumber, req.UploadID(), err)
			return err
		}

		if _, err := f.Write(req.Chunk); err != nil {
			app.Log.Critf("Failed writing chunk #%d for %s: %s", req.FlowChunkNumber, req.UploadID(), err)
			return err
		}
		return nil
	}
}

type mockRequestWriter struct {
	err error
}

func (r *mockRequestWriter) write(dir string, req *flow.Request) error {
	return r.err
}
<|endoftext|>