<commit_before>package servers

import (
	"encoding/base64"
	"fmt"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// List makes a request against the API to list servers accessible to you.
func List(client *gophercloud.ServiceClient) pagination.Pager {
	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
		return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
	}

	return pagination.NewPager(client, listDetailURL(client), createPage)
}

// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsBuilder interface {
	ToServerCreateMap() map[string]interface{}
}

// Network is used within CreateOpts to control a new server's network attachments.
type Network struct {
	// UUID of a nova-network to attach to the newly provisioned server.
	// Required unless Port is provided.
	UUID string

	// Port of a neutron network to attach to the newly provisioned server.
	// Required unless UUID is provided.
	Port string

	// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
	FixedIP string
}

// CreateOpts specifies server creation parameters.
type CreateOpts struct {
	// Name [required] is the name to assign to the newly launched server.
	Name string

	// ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
	// Optional if using the boot-from-volume extension.
	ImageRef string

	// FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
	FlavorRef string

	// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
	SecurityGroups []string

	// UserData [optional] contains configuration information or scripts to use upon launch.
	// Create will base64-encode it for you.
	UserData []byte

	// AvailabilityZone [optional] in which to launch the server.
	AvailabilityZone string

	// Networks [optional] dictates how this server will be attached to available networks.
	// By default, the server will be attached to all isolated networks for the tenant.
	Networks []Network

	// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
	Metadata map[string]string

	// Personality [optional] includes the path and contents of a file to inject into the server at launch.
	// The maximum size of the file is 255 bytes (decoded).
	Personality []byte

	// ConfigDrive [optional] enables metadata injection through a configuration drive.
	ConfigDrive bool
}

// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
	server := make(map[string]interface{})

	server["name"] = opts.Name
	server["imageRef"] = opts.ImageRef
	server["flavorRef"] = opts.FlavorRef

	if opts.UserData != nil {
		encoded := base64.StdEncoding.EncodeToString(opts.UserData)
		server["user_data"] = &encoded
	}
	if opts.Personality != nil {
		encoded := base64.StdEncoding.EncodeToString(opts.Personality)
		server["personality"] = &encoded
	}
	if opts.ConfigDrive {
		server["config_drive"] = "true"
	}
	if opts.AvailabilityZone != "" {
		server["availability_zone"] = opts.AvailabilityZone
	}
	if opts.Metadata != nil {
		server["metadata"] = opts.Metadata
	}

	if len(opts.SecurityGroups) > 0 {
		securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
		for i, groupName := range opts.SecurityGroups {
			securityGroups[i] = map[string]interface{}{"name": groupName}
		}
	}
	if len(opts.Networks) > 0 {
		networks := make([]map[string]interface{}, len(opts.Networks))
		for i, net := range opts.Networks {
			networks[i] = make(map[string]interface{})
			if net.UUID != "" {
				networks[i]["uuid"] = net.UUID
			}
			if net.Port != "" {
				networks[i]["port"] = net.Port
			}
			if net.FixedIP != "" {
				networks[i]["fixed_ip"] = net.FixedIP
			}
		}
	}

	return map[string]interface{}{"server": server}
}

// Create requests a server to be provisioned to the user in the current tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var result CreateResult
	_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
		Results:     &result.Resp,
		ReqBody:     opts.ToServerCreateMap(),
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return result
}

// Delete requests that a server previously provisioned be removed from your account.
func Delete(client *gophercloud.ServiceClient, id string) error {
	_, err := perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{204},
	})
	return err
}

// Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
	var result GetResult
	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
		Results:     &result.Resp,
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
	})
	return result
}

// UpdateOptsLike allows extentions to add additional attributes to the Update request.
type UpdateOptsLike interface {
	ToServerUpdateMap() map[string]interface{}
}

// UpdateOpts specifies the base attributes that may be updated on an existing server.
type UpdateOpts struct {
	// Name [optional] changes the displayed name of the server.
	// The server host name will *not* change.
	// Server names are not constrained to be unique, even within the same tenant.
	Name string

	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
	AccessIPv4 string

	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
	AccessIPv6 string
}

// ToServerUpdateMap formats an UpdateOpts structure into a request body.
func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
	server := make(map[string]string)
	if opts.Name != "" {
		server["name"] = opts.Name
	}
	if opts.AccessIPv4 != "" {
		server["accessIPv4"] = opts.AccessIPv4
	}
	if opts.AccessIPv6 != "" {
		server["accessIPv6"] = opts.AccessIPv6
	}
	return map[string]interface{}{"server": server}
}

// Update requests that various attributes of the indicated server be changed.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsLike) UpdateResult {
	var result UpdateResult
	_, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
		Results:     &result.Resp,
		ReqBody:     opts.ToServerUpdateMap(),
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
	})
	return result
}

// ChangeAdminPassword alters the administrator or root password for a specified server.
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
	var req struct {
		ChangePassword struct {
			AdminPass string `json:"adminPass"`
		} `json:"changePassword"`
	}

	req.ChangePassword.AdminPass = newPassword

	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody:     req,
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}

// ErrArgument errors occur when an argument supplied to a package function
// fails to fall within acceptable values.  For example, the Reboot() function
// expects the "how" parameter to be one of HardReboot or SoftReboot.  These
// constants are (currently) strings, leading someone to wonder if they can pass
// other string values instead, perhaps in an effort to break the API of their
// provider.  Reboot() returns this error in this situation.
//
// Function identifies which function was called/which function is generating
// the error.
// Argument identifies which formal argument was responsible for producing the
// error.
// Value provides the value as it was passed into the function.
type ErrArgument struct {
	Function, Argument string
	Value              interface{}
}

// Error yields a useful diagnostic for debugging purposes.
func (e *ErrArgument) Error() string {
	return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
}

func (e *ErrArgument) String() string {
	return e.Error()
}

// RebootMethod describes the mechanisms by which a server reboot can be requested.
type RebootMethod string

// These constants determine how a server should be rebooted.
// See the Reboot() function for further details.
const (
	SoftReboot RebootMethod = "SOFT"
	HardReboot RebootMethod = "HARD"
	OSReboot                = SoftReboot
	PowerCycle              = HardReboot
)

// Reboot requests that a given server reboot.
// Two methods exist for rebooting a server:
//
// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
// terminating it at the hypervisor level.
// It's done. Caput. Full stop.
// Then, after a brief while, power is restored or the VM instance restarted.
//
// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) error {
	if (how != SoftReboot) && (how != HardReboot) {
		return &ErrArgument{
			Function: "Reboot",
			Argument: "how",
			Value:    how,
		}
	}

	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody: struct {
			C map[string]string `json:"reboot"`
		}{
			map[string]string{"type": string(how)},
		},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}

// Rebuild requests that the Openstack provider reprovision the server.
// The rebuild will need to know the server's name and new image reference or ID.
// In addition, and unlike building a server with Create(), you must provide an administrator password.
//
// Additional options may be specified with the additional map.
// This function treats a nil map the same as an empty map.
//
// Rebuild returns a server result as though you had called GetDetail() on the server's ID.
// The information, however, refers to the new server, not the old.
func Rebuild(client *gophercloud.ServiceClient, id, name, password, imageRef string, additional map[string]interface{}) RebuildResult {
	var result RebuildResult

	if id == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "id",
			Value:    "",
		}
		return result
	}

	if name == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "name",
			Value:    "",
		}
		return result
	}

	if password == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "password",
			Value:    "",
		}
		return result
	}

	if imageRef == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "imageRef",
			Value:    "",
		}
		return result
	}

	if additional == nil {
		additional = make(map[string]interface{}, 0)
	}

	additional["name"] = name
	additional["imageRef"] = imageRef
	additional["adminPass"] = password

	_, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody: struct {
			R map[string]interface{} `json:"rebuild"`
		}{
			additional,
		},
		Results:     &result.Resp,
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return result
}

// Resize instructs the provider to change the flavor of the server.
// Note that this implies rebuilding it.
// Unfortunately, one cannot pass rebuild parameters to the resize function.
// When the resize completes, the server will be in RESIZE_VERIFY state.
// While in this state, you can explore the use of the new server's configuration.
// If you like it, call ConfirmResize() to commit the resize permanently.
// Otherwise, call RevertResize() to restore the old configuration.
func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody: struct {
			R map[string]interface{} `json:"resize"`
		}{
			map[string]interface{}{"flavorRef": flavorRef},
		},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}

// ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details.
func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody:     map[string]interface{}{"confirmResize": nil},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{204},
	})
	return err
}

// RevertResize cancels a previous resize operation on a server.
// See Resize() for more details.
func RevertResize(client *gophercloud.ServiceClient, id string) error {
	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody:     map[string]interface{}{"revertResize": nil},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}
<commit_msg>UpdateOptsLike -> UpdateOptsBuilder<commit_after>package servers

import (
	"encoding/base64"
	"fmt"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// List makes a request against the API to list servers accessible to you.
func List(client *gophercloud.ServiceClient) pagination.Pager {
	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
		return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
	}

	return pagination.NewPager(client, listDetailURL(client), createPage)
}

// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsBuilder interface {
	ToServerCreateMap() map[string]interface{}
}

// Network is used within CreateOpts to control a new server's network attachments.
type Network struct {
	// UUID of a nova-network to attach to the newly provisioned server.
	// Required unless Port is provided.
	UUID string

	// Port of a neutron network to attach to the newly provisioned server.
	// Required unless UUID is provided.
	Port string

	// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
	FixedIP string
}

// CreateOpts specifies server creation parameters.
type CreateOpts struct {
	// Name [required] is the name to assign to the newly launched server.
	Name string

	// ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
	// Optional if using the boot-from-volume extension.
	ImageRef string

	// FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
	FlavorRef string

	// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
	SecurityGroups []string

	// UserData [optional] contains configuration information or scripts to use upon launch.
	// Create will base64-encode it for you.
	UserData []byte

	// AvailabilityZone [optional] in which to launch the server.
	AvailabilityZone string

	// Networks [optional] dictates how this server will be attached to available networks.
	// By default, the server will be attached to all isolated networks for the tenant.
	Networks []Network

	// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
	Metadata map[string]string

	// Personality [optional] includes the path and contents of a file to inject into the server at launch.
	// The maximum size of the file is 255 bytes (decoded).
	Personality []byte

	// ConfigDrive [optional] enables metadata injection through a configuration drive.
	ConfigDrive bool
}

// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
	server := make(map[string]interface{})

	server["name"] = opts.Name
	server["imageRef"] = opts.ImageRef
	server["flavorRef"] = opts.FlavorRef

	if opts.UserData != nil {
		encoded := base64.StdEncoding.EncodeToString(opts.UserData)
		server["user_data"] = &encoded
	}
	if opts.Personality != nil {
		encoded := base64.StdEncoding.EncodeToString(opts.Personality)
		server["personality"] = &encoded
	}
	if opts.ConfigDrive {
		server["config_drive"] = "true"
	}
	if opts.AvailabilityZone != "" {
		server["availability_zone"] = opts.AvailabilityZone
	}
	if opts.Metadata != nil {
		server["metadata"] = opts.Metadata
	}

	if len(opts.SecurityGroups) > 0 {
		securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
		for i, groupName := range opts.SecurityGroups {
			securityGroups[i] = map[string]interface{}{"name": groupName}
		}
	}
	if len(opts.Networks) > 0 {
		networks := make([]map[string]interface{}, len(opts.Networks))
		for i, net := range opts.Networks {
			networks[i] = make(map[string]interface{})
			if net.UUID != "" {
				networks[i]["uuid"] = net.UUID
			}
			if net.Port != "" {
				networks[i]["port"] = net.Port
			}
			if net.FixedIP != "" {
				networks[i]["fixed_ip"] = net.FixedIP
			}
		}
	}

	return map[string]interface{}{"server": server}
}

// Create requests a server to be provisioned to the user in the current tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var result CreateResult
	_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
		Results:     &result.Resp,
		ReqBody:     opts.ToServerCreateMap(),
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return result
}

// Delete requests that a server previously provisioned be removed from your account.
func Delete(client *gophercloud.ServiceClient, id string) error {
	_, err := perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{204},
	})
	return err
}

// Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
	var result GetResult
	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
		Results:     &result.Resp,
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
	})
	return result
}

// UpdateOptsBuilder allows extentions to add additional attributes to the Update request.
type UpdateOptsBuilder interface {
	ToServerUpdateMap() map[string]interface{}
}

// UpdateOpts specifies the base attributes that may be updated on an existing server.
type UpdateOpts struct {
	// Name [optional] changes the displayed name of the server.
	// The server host name will *not* change.
	// Server names are not constrained to be unique, even within the same tenant.
	Name string

	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
	AccessIPv4 string

	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
	AccessIPv6 string
}

// ToServerUpdateMap formats an UpdateOpts structure into a request body.
func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
	server := make(map[string]string)
	if opts.Name != "" {
		server["name"] = opts.Name
	}
	if opts.AccessIPv4 != "" {
		server["accessIPv4"] = opts.AccessIPv4
	}
	if opts.AccessIPv6 != "" {
		server["accessIPv6"] = opts.AccessIPv6
	}
	return map[string]interface{}{"server": server}
}

// Update requests that various attributes of the indicated server be changed.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
	var result UpdateResult
	_, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
		Results:     &result.Resp,
		ReqBody:     opts.ToServerUpdateMap(),
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
	})
	return result
}

// ChangeAdminPassword alters the administrator or root password for a specified server.
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
	var req struct {
		ChangePassword struct {
			AdminPass string `json:"adminPass"`
		} `json:"changePassword"`
	}

	req.ChangePassword.AdminPass = newPassword

	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody:     req,
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}

// ErrArgument errors occur when an argument supplied to a package function
// fails to fall within acceptable values.  For example, the Reboot() function
// expects the "how" parameter to be one of HardReboot or SoftReboot.  These
// constants are (currently) strings, leading someone to wonder if they can pass
// other string values instead, perhaps in an effort to break the API of their
// provider.  Reboot() returns this error in this situation.
//
// Function identifies which function was called/which function is generating
// the error.
// Argument identifies which formal argument was responsible for producing the
// error.
// Value provides the value as it was passed into the function.
type ErrArgument struct {
	Function, Argument string
	Value              interface{}
}

// Error yields a useful diagnostic for debugging purposes.
func (e *ErrArgument) Error() string {
	return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
}

func (e *ErrArgument) String() string {
	return e.Error()
}

// RebootMethod describes the mechanisms by which a server reboot can be requested.
type RebootMethod string

// These constants determine how a server should be rebooted.
// See the Reboot() function for further details.
const (
	SoftReboot RebootMethod = "SOFT"
	HardReboot RebootMethod = "HARD"
	OSReboot                = SoftReboot
	PowerCycle              = HardReboot
)

// Reboot requests that a given server reboot.
// Two methods exist for rebooting a server:
//
// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
// terminating it at the hypervisor level.
// It's done. Caput. Full stop.
// Then, after a brief while, power is restored or the VM instance restarted.
//
// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) error {
	if (how != SoftReboot) && (how != HardReboot) {
		return &ErrArgument{
			Function: "Reboot",
			Argument: "how",
			Value:    how,
		}
	}

	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody: struct {
			C map[string]string `json:"reboot"`
		}{
			map[string]string{"type": string(how)},
		},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}

// Rebuild requests that the Openstack provider reprovision the server.
// The rebuild will need to know the server's name and new image reference or ID.
// In addition, and unlike building a server with Create(), you must provide an administrator password.
//
// Additional options may be specified with the additional map.
// This function treats a nil map the same as an empty map.
//
// Rebuild returns a server result as though you had called GetDetail() on the server's ID.
// The information, however, refers to the new server, not the old.
func Rebuild(client *gophercloud.ServiceClient, id, name, password, imageRef string, additional map[string]interface{}) RebuildResult {
	var result RebuildResult

	if id == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "id",
			Value:    "",
		}
		return result
	}

	if name == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "name",
			Value:    "",
		}
		return result
	}

	if password == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "password",
			Value:    "",
		}
		return result
	}

	if imageRef == "" {
		result.Err = &ErrArgument{
			Function: "Rebuild",
			Argument: "imageRef",
			Value:    "",
		}
		return result
	}

	if additional == nil {
		additional = make(map[string]interface{}, 0)
	}

	additional["name"] = name
	additional["imageRef"] = imageRef
	additional["adminPass"] = password

	_, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody: struct {
			R map[string]interface{} `json:"rebuild"`
		}{
			additional,
		},
		Results:     &result.Resp,
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return result
}

// Resize instructs the provider to change the flavor of the server.
// Note that this implies rebuilding it.
// Unfortunately, one cannot pass rebuild parameters to the resize function.
// When the resize completes, the server will be in RESIZE_VERIFY state.
// While in this state, you can explore the use of the new server's configuration.
// If you like it, call ConfirmResize() to commit the resize permanently.
// Otherwise, call RevertResize() to restore the old configuration.
func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody: struct {
			R map[string]interface{} `json:"resize"`
		}{
			map[string]interface{}{"flavorRef": flavorRef},
		},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}

// ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details.
func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody:     map[string]interface{}{"confirmResize": nil},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{204},
	})
	return err
}

// RevertResize cancels a previous resize operation on a server.
// See Resize() for more details.
func RevertResize(client *gophercloud.ServiceClient, id string) error {
	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
		ReqBody:     map[string]interface{}{"revertResize": nil},
		MoreHeaders: client.Provider.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return err
}
<|endoftext|>


<commit_before>package disasm

import (
	"reflect"
	"testing"
)

func TestParseOpcode(t *testing.T) {
	modrmTests := []struct {
		bs   []byte
		want command
	}{
		// add
		{[]byte{0x0, 0x0}, command{nil, add, 2, 0, 0, 0}},
		{[]byte{0x1, 0x0}, command{nil, add, 2, 0, 0, 1}},
		{[]byte{0x2, 0x0}, command{nil, add, 2, 0, 1, 0}},
		{[]byte{0x3, 0x0}, command{nil, add, 2, 0, 1, 1}},
	}

	got := &command{}

	for _, tt := range modrmTests {
		err := got.parseOpcode(tt.bs)
		if err != nil {
			t.Errorf("%v", err)
		}
		if reflect.DeepEqual(got, tt.want) {
			t.Errorf("got %v; want %v", got, tt.want)
		}
	}
}
<commit_msg>disasm: add command#parseOpcode test for add, push, pop<commit_after>package disasm

import (
	"reflect"
	"testing"
)

func TestParseOpcode(t *testing.T) {
	modrmTests := []struct {
		bs   []byte
		want *command
	}{
		// add
		{[]byte{0x0, 0x0}, &command{bs: nil, mnem: add, l: 2, d: 0, w: 0, reg: 0}},
		{[]byte{0x1, 0x0}, &command{bs: nil, mnem: add, l: 2, d: 0, w: 1, reg: 0}},
		{[]byte{0x2, 0x0}, &command{bs: nil, mnem: add, l: 2, d: 1, w: 0, reg: 0}},
		{[]byte{0x3, 0x0}, &command{bs: nil, mnem: add, l: 2, d: 1, w: 1, reg: 0}},
		{[]byte{0x4, 0x0}, &command{bs: nil, mnem: add, l: 1, d: 0, w: 0, reg: 0}},
		{[]byte{0x5, 0x0}, &command{bs: nil, mnem: add, l: 2, d: 0, w: 1, reg: 0}},
		// push
		{[]byte{0x6, 0x0}, &command{bs: nil, mnem: push, l: 1, d: 0, w: 0, reg: 0}},
		// pop
		{[]byte{0x7, 0x0}, &command{bs: nil, mnem: pop, l: 1, d: 0, w: 0, reg: 0}},
	}

	got := &command{}

	for _, tt := range modrmTests {
		err := got.parseOpcode(tt.bs)
		if err != nil {
			t.Errorf("%v", err)
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("got %v; want %v", got, tt.want)
		}
	}
}
<|endoftext|>


<commit_before>package bloom_filter

import (
	"math"
)

type BloomFilter struct {
	Capacity    int
	Probability float64
	size        int
	hashTable   []int
	hashesNum   int
}

func NewBloomFilter(probability float64, capacity int) *BloomFilter {
	bloomFilter := new(BloomFilter)
	bloomFilter.Probability = probability
	bloomFilter.Capacity = capacity

	bloomFilter.size = int(math.Ceil(
		(float64(capacity) * math.Log(probability)) / math.Log(1/math.Pow(2, math.Log(2))),
	))

	bloomFilter.hashTable = make([]int, bloomFilter.size)
	bloomFilter.hashesNum = int(math.Round(math.Log(2) * float64(bloomFilter.size/capacity)))

	return bloomFilter
}

func (b *BloomFilter) hashFunc(key string, seed int) int {
	h := 0
	for _, char := range key {
		h += int(char) + seed
	}
	return h % b.size
}

func (b *BloomFilter) Add(key string) {
	for seed := 1; seed <= b.hashesNum+1; seed++ {
		b.hashTable[b.hashFunc(key, seed)] = 1
	}
}

func (b *BloomFilter) Check(key string) bool {
	for seed := 1; seed <= b.hashesNum+1; seed++ {
		if b.hashTable[b.hashFunc(key, seed)] == 0 {
			return false
		}
	}

	return true
}
<commit_msg>Add GetSize method for BloomFilter<commit_after>package bloom_filter

import (
	"math"
)

type BloomFilter struct {
	Capacity    int
	Probability float64
	size        int
	hashTable   []int
	hashesNum   int
}

func NewBloomFilter(probability float64, capacity int) *BloomFilter {
	bloomFilter := new(BloomFilter)
	bloomFilter.Probability = probability
	bloomFilter.Capacity = capacity

	bloomFilter.size = int(math.Ceil(
		(float64(capacity) * math.Log(probability)) / math.Log(1/math.Pow(2, math.Log(2))),
	))

	bloomFilter.hashTable = make([]int, bloomFilter.size)
	bloomFilter.hashesNum = int(math.Round(math.Log(2) * float64(bloomFilter.size/capacity)))

	return bloomFilter
}

func (b *BloomFilter) hashFunc(key string, seed int) int {
	h := 0
	for _, char := range key {
		h += int(char) + seed
	}
	return h % b.size
}

func (b *BloomFilter) Add(key string) {
	for seed := 1; seed <= b.hashesNum+1; seed++ {
		b.hashTable[b.hashFunc(key, seed)] = 1
	}
}

func (b *BloomFilter) Check(key string) bool {
	for seed := 1; seed <= b.hashesNum+1; seed++ {
		if b.hashTable[b.hashFunc(key, seed)] == 0 {
			return false
		}
	}

	return true
}

func (b *BloomFilter) GetSize() int {
	return b.size
}
<|endoftext|>


<commit_before>package validate

import (
	"testing"

	"github.com/opencontainers/runc/libcontainer/configs"
)

func init() {
	geteuid = func() int { return 1337 }
	getegid = func() int { return 7331 }
}

func rootlessConfig() *configs.Config {
	return &configs.Config{
		Rootfs:   "/var",
		Rootless: true,
		Namespaces: configs.Namespaces(
			[]configs.Namespace{
				{Type: configs.NEWUSER},
			},
		),
		UidMappings: []configs.IDMap{
			{
				HostID:      geteuid(),
				ContainerID: 0,
				Size:        1,
			},
		},
		GidMappings: []configs.IDMap{
			{
				HostID:      getegid(),
				ContainerID: 0,
				Size:        1,
			},
		},
	}
}

func TestValidateRootless(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur: %+v", err)
	}
}

/* rootlessMappings() */

func TestValidateRootlessUserns(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.Namespaces = nil
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if user namespaces not set")
	}
}

func TestValidateRootlessMappingUid(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.UidMappings = nil
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if no uid mappings provided")
	}

	config = rootlessConfig()
	config.UidMappings[0].HostID = geteuid() + 1
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if geteuid() != mapped uid")
	}

	config = rootlessConfig()
	config.UidMappings[0].Size = 1024
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if more than one uid mapped")
	}

	config = rootlessConfig()
	config.UidMappings = append(config.UidMappings, configs.IDMap{
		HostID:      geteuid() + 1,
		ContainerID: 0,
		Size:        1,
	})
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if more than one uid extent mapped")
	}
}

func TestValidateRootlessMappingGid(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.GidMappings = nil
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if no gid mappings provided")
	}

	config = rootlessConfig()
	config.GidMappings[0].HostID = getegid() + 1
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if getegid() != mapped gid")
	}

	config = rootlessConfig()
	config.GidMappings[0].Size = 1024
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if more than one gid mapped")
	}

	config = rootlessConfig()
	config.GidMappings = append(config.GidMappings, configs.IDMap{
		HostID:      getegid() + 1,
		ContainerID: 0,
		Size:        1,
	})
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if more than one gid extent mapped")
	}
}

/* rootlessMount() */

func TestValidateRootlessMountUid(t *testing.T) {
	config := rootlessConfig()
	validator := New()

	config.Mounts = []*configs.Mount{
		{
			Source:      "devpts",
			Destination: "/dev/pts",
			Device:      "devpts",
		},
	}

	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when uid= not set in mount options: %+v", err)
	}

	config.Mounts[0].Data = "uid=5"
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur when setting uid=5 in mount options")
	}

	config.Mounts[0].Data = "uid=0"
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when setting uid=0 in mount options: %+v", err)
	}
}

func TestValidateRootlessMountGid(t *testing.T) {
	config := rootlessConfig()
	validator := New()

	config.Mounts = []*configs.Mount{
		{
			Source:      "devpts",
			Destination: "/dev/pts",
			Device:      "devpts",
		},
	}

	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when gid= not set in mount options: %+v", err)
	}

	config.Mounts[0].Data = "gid=5"
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur when setting gid=5 in mount options")
	}

	config.Mounts[0].Data = "gid=0"
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when setting gid=0 in mount options: %+v", err)
	}
}

/* rootlessCgroup() */

func TestValidateRootlessCgroup(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.Cgroups = &configs.Cgroup{
		Resources: &configs.Resources{
			PidsLimit: 1337,
		},
	}
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if cgroup limits set")
	}
}
<commit_msg>tests: fix for rootless multiple uids/gids<commit_after>package validate

import (
	"testing"

	"github.com/opencontainers/runc/libcontainer/configs"
)

func init() {
	geteuid = func() int { return 1337 }
	getegid = func() int { return 7331 }
}

func rootlessConfig() *configs.Config {
	return &configs.Config{
		Rootfs:   "/var",
		Rootless: true,
		Namespaces: configs.Namespaces(
			[]configs.Namespace{
				{Type: configs.NEWUSER},
			},
		),
		UidMappings: []configs.IDMap{
			{
				HostID:      geteuid(),
				ContainerID: 0,
				Size:        1,
			},
		},
		GidMappings: []configs.IDMap{
			{
				HostID:      getegid(),
				ContainerID: 0,
				Size:        1,
			},
		},
	}
}

func TestValidateRootless(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur: %+v", err)
	}
}

/* rootlessMappings() */

func TestValidateRootlessUserns(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.Namespaces = nil
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if user namespaces not set")
	}
}

func TestValidateRootlessMappingUid(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.UidMappings = nil
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if no uid mappings provided")
	}
}

func TestValidateRootlessMappingGid(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.GidMappings = nil
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if no gid mappings provided")
	}
}

/* rootlessMount() */

func TestValidateRootlessMountUid(t *testing.T) {
	config := rootlessConfig()
	validator := New()

	config.Mounts = []*configs.Mount{
		{
			Source:      "devpts",
			Destination: "/dev/pts",
			Device:      "devpts",
		},
	}

	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when uid= not set in mount options: %+v", err)
	}

	config.Mounts[0].Data = "uid=5"
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur when setting uid=5 in mount options")
	}

	config.Mounts[0].Data = "uid=0"
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when setting uid=0 in mount options: %+v", err)
	}

	config.Mounts[0].Data = "uid=2"
	config.UidMappings[0].Size = 10
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when setting uid=2 in mount options and UidMapping[0].size is 10")
	}

	config.Mounts[0].Data = "uid=20"
	config.UidMappings[0].Size = 10
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur when setting uid=20 in mount options and UidMapping[0].size is 10")
	}
}

func TestValidateRootlessMountGid(t *testing.T) {
	config := rootlessConfig()
	validator := New()

	config.Mounts = []*configs.Mount{
		{
			Source:      "devpts",
			Destination: "/dev/pts",
			Device:      "devpts",
		},
	}

	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when gid= not set in mount options: %+v", err)
	}

	config.Mounts[0].Data = "gid=5"
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur when setting gid=5 in mount options")
	}

	config.Mounts[0].Data = "gid=0"
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when setting gid=0 in mount options: %+v", err)
	}

	config.Mounts[0].Data = "gid=5"
	config.GidMappings[0].Size = 10
	if err := validator.Validate(config); err != nil {
		t.Errorf("Expected error to not occur when setting gid=5 in mount options and GidMapping[0].size is 10")
	}

	config.Mounts[0].Data = "gid=11"
	config.GidMappings[0].Size = 10
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur when setting gid=11 in mount options and GidMapping[0].size is 10")
	}
}

/* rootlessCgroup() */

func TestValidateRootlessCgroup(t *testing.T) {
	validator := New()

	config := rootlessConfig()
	config.Cgroups = &configs.Cgroup{
		Resources: &configs.Resources{
			PidsLimit: 1337,
		},
	}
	if err := validator.Validate(config); err == nil {
		t.Errorf("Expected error to occur if cgroup limits set")
	}
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tag

import (
	"fmt"
	"io"
	"strconv"
)

// ID3v2Header is a type which represents an ID3v2 tag header.
type ID3v2Header struct {
	Version           Format
	Unsynchronisation bool
	ExtendedHeader    bool
	Experimental      bool
	Size              int
}

// readID3v2Header reads the ID3v2 header from the given io.Reader.
func readID3v2Header(r io.Reader) (*ID3v2Header, error) {
	b, err := readBytes(r, 10)
	if err != nil {
		return nil, fmt.Errorf("expected to read 10 bytes (ID3v2Header): %v", err)
	}

	if string(b[0:3]) != "ID3" {
		return nil, fmt.Errorf("expected to read \"ID3\"")
	}

	b = b[3:]
	var vers Format
	switch uint(b[0]) {
	case 2:
		vers = ID3v2_2
	case 3:
		vers = ID3v2_3
	case 4:
		vers = ID3v2_4
	case 0, 1:
		fallthrough
	default:
		return nil, fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0]))
	}

	// NB: We ignore b[1] (the revision) as we don't currently rely on it.
	return &ID3v2Header{
		Version:           vers,
		Unsynchronisation: getBit(b[2], 7),
		ExtendedHeader:    getBit(b[2], 6),
		Experimental:      getBit(b[2], 5),
		Size:              get7BitChunkedInt(b[3:7]),
	}, nil
}

// ID3v2FrameFlags is a type which represents the flags which can be set on an ID3v2 frame.
type ID3v2FrameFlags struct {
	// Message
	TagAlterPreservation  bool
	FileAlterPreservation bool
	ReadOnly              bool

	// Format
	GroupIdentity       bool
	Compression         bool
	Encryption          bool
	Unsynchronisation   bool
	DataLengthIndicator bool
}

func readID3v2FrameFlags(r io.Reader) (*ID3v2FrameFlags, error) {
	b, err := readBytes(r, 2)
	if err != nil {
		return nil, err
	}

	msg := b[0]
	fmt := b[1]

	return &ID3v2FrameFlags{
		TagAlterPreservation:  getBit(msg, 6),
		FileAlterPreservation: getBit(msg, 5),
		ReadOnly:              getBit(msg, 4),
		GroupIdentity:         getBit(fmt, 7),
		Compression:           getBit(fmt, 3),
		Encryption:            getBit(fmt, 2),
		Unsynchronisation:     getBit(fmt, 1),
		DataLengthIndicator:   getBit(fmt, 0),
	}, nil
}

func readID3v2_2FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
	name, err = readString(r, 3)
	if err != nil {
		return
	}
	size, err = readInt(r, 3)
	if err != nil {
		return
	}
	headerSize = 6
	return
}

func readID3v2_3FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
	name, err = readString(r, 4)
	if err != nil {
		return
	}
	size, err = readInt(r, 4)
	if err != nil {
		return
	}
	headerSize = 8
	return
}

func readID3v2_4FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
	name, err = readString(r, 4)
	if err != nil {
		return
	}
	size, err = read7BitChunkedInt(r, 4)
	if err != nil {
		return
	}
	headerSize = 8
	return
}

// readID3v2Frames reads ID3v2 frames from the given reader using the ID3v2Header.
func readID3v2Frames(r io.Reader, h *ID3v2Header) (map[string]interface{}, error) {
	offset := 10 // the size of the header
	result := make(map[string]interface{})

	for offset < h.Size {
		var err error
		var name string
		var size, headerSize int
		var flags *ID3v2FrameFlags

		switch h.Version {
		case ID3v2_2:
			name, size, headerSize, err = readID3v2_2FrameHeader(r)

		case ID3v2_3:
			name, size, headerSize, err = readID3v2_3FrameHeader(r)
			if err != nil {
				return nil, err
			}
			flags, err = readID3v2FrameFlags(r)
			headerSize += 2

		case ID3v2_4:
			name, size, headerSize, err = readID3v2_4FrameHeader(r)
			if err != nil {
				return nil, err
			}
			flags, err = readID3v2FrameFlags(r)
			headerSize += 2
		}

		if err != nil {
			return nil, err
		}

		// Avoid corrupted padding (see http://id3.org/Compliance%20Issues).
		if !validID3Frame(h.Version, name) {
			break
		}

		// FIXME: Do we still need this?
		// if size=0, we certainly are in a padding zone. ignore the rest of
		// the tags
		if size == 0 {
			break
		}

		offset += headerSize + size

		// Check this stuff out...
		if flags != nil && flags.DataLengthIndicator {
			_, err = read7BitChunkedInt(r, 4) // read 4
			if err != nil {
				return nil, err
			}
			size -= 4
		}

		if flags != nil && flags.Unsynchronisation {
			// FIXME: Implement this.
			continue
		}

		b, err := readBytes(r, size)
		if err != nil {
			return nil, err
		}

		// There can be multiple tag with the same name. Append a number to the
		// name if there is more than one.
		rawName := name
		if _, ok := result[rawName]; ok {
			for i := 0; ok; i++ {
				rawName = name + "_" + strconv.Itoa(i)
				_, ok = result[rawName]
			}
		}

		switch {
		case name == "TXXX" || name == "TXX":
			t, err := readTextWithDescrFrame(b, false, true) // no lang, but enc
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name[0] == 'T':
			txt, err := readTFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = txt

		case name == "UFID" || name == "UFI":
			t, err := readUFID(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name == "WXXX" || name == "WXX":
			t, err := readTextWithDescrFrame(b, false, false) // no lang, no enc
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name[0] == 'W':
			txt, err := readWFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = txt

		case name == "COMM" || name == "USLT":
			t, err := readTextWithDescrFrame(b, true, true) // both lang and enc
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name == "APIC":
			p, err := readAPICFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = p

		case name == "PIC":
			p, err := readPICFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = p
		default:
			result[rawName] = b
		}

		continue
	}
	return result, nil
}

type unsynchroniser struct {
	io.Reader
	ff bool
}

// filter io.Reader which skip the Unsynchronisation bytes
func (r *unsynchroniser) Read(p []byte) (int, error) {
	b := make([]byte, 1)
	i := 0
	for i < len(p) {
		if n, err := r.Reader.Read(b); err != nil || n == 0 {
			return i, err
		}
		if r.ff && b[0] == 0x00 {
			r.ff = false
			continue
		}
		p[i] = b[0]
		i++
		r.ff = (b[0] == 0xFF)
	}
	return i, nil
}

// ReadID3v2Tags parses ID3v2.{2,3,4} tags from the io.ReadSeeker into a Metadata, returning
// non-nil error on failure.
func ReadID3v2Tags(r io.ReadSeeker) (Metadata, error) {
	h, err := readID3v2Header(r)
	if err != nil {
		return nil, err
	}

	var ur io.Reader
	ur = r
	if h.Unsynchronisation {
		ur = &unsynchroniser{Reader: r}
	}

	f, err := readID3v2Frames(ur, h)
	if err != nil {
		return nil, err
	}
	return metadataID3v2{header: h, frames: f}, nil
}
<commit_msg>Remove unnecessary continue, and add some spacing.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tag

import (
	"fmt"
	"io"
	"strconv"
)

// ID3v2Header is a type which represents an ID3v2 tag header.
type ID3v2Header struct {
	Version           Format
	Unsynchronisation bool
	ExtendedHeader    bool
	Experimental      bool
	Size              int
}

// readID3v2Header reads the ID3v2 header from the given io.Reader.
func readID3v2Header(r io.Reader) (*ID3v2Header, error) {
	b, err := readBytes(r, 10)
	if err != nil {
		return nil, fmt.Errorf("expected to read 10 bytes (ID3v2Header): %v", err)
	}

	if string(b[0:3]) != "ID3" {
		return nil, fmt.Errorf("expected to read \"ID3\"")
	}

	b = b[3:]
	var vers Format
	switch uint(b[0]) {
	case 2:
		vers = ID3v2_2
	case 3:
		vers = ID3v2_3
	case 4:
		vers = ID3v2_4
	case 0, 1:
		fallthrough
	default:
		return nil, fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0]))
	}

	// NB: We ignore b[1] (the revision) as we don't currently rely on it.
	return &ID3v2Header{
		Version:           vers,
		Unsynchronisation: getBit(b[2], 7),
		ExtendedHeader:    getBit(b[2], 6),
		Experimental:      getBit(b[2], 5),
		Size:              get7BitChunkedInt(b[3:7]),
	}, nil
}

// ID3v2FrameFlags is a type which represents the flags which can be set on an ID3v2 frame.
type ID3v2FrameFlags struct {
	// Message
	TagAlterPreservation  bool
	FileAlterPreservation bool
	ReadOnly              bool

	// Format
	GroupIdentity       bool
	Compression         bool
	Encryption          bool
	Unsynchronisation   bool
	DataLengthIndicator bool
}

func readID3v2FrameFlags(r io.Reader) (*ID3v2FrameFlags, error) {
	b, err := readBytes(r, 2)
	if err != nil {
		return nil, err
	}

	msg := b[0]
	fmt := b[1]

	return &ID3v2FrameFlags{
		TagAlterPreservation:  getBit(msg, 6),
		FileAlterPreservation: getBit(msg, 5),
		ReadOnly:              getBit(msg, 4),
		GroupIdentity:         getBit(fmt, 7),
		Compression:           getBit(fmt, 3),
		Encryption:            getBit(fmt, 2),
		Unsynchronisation:     getBit(fmt, 1),
		DataLengthIndicator:   getBit(fmt, 0),
	}, nil
}

func readID3v2_2FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
	name, err = readString(r, 3)
	if err != nil {
		return
	}
	size, err = readInt(r, 3)
	if err != nil {
		return
	}
	headerSize = 6
	return
}

func readID3v2_3FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
	name, err = readString(r, 4)
	if err != nil {
		return
	}
	size, err = readInt(r, 4)
	if err != nil {
		return
	}
	headerSize = 8
	return
}

func readID3v2_4FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
	name, err = readString(r, 4)
	if err != nil {
		return
	}
	size, err = read7BitChunkedInt(r, 4)
	if err != nil {
		return
	}
	headerSize = 8
	return
}

// readID3v2Frames reads ID3v2 frames from the given reader using the ID3v2Header.
func readID3v2Frames(r io.Reader, h *ID3v2Header) (map[string]interface{}, error) {
	offset := 10 // the size of the header
	result := make(map[string]interface{})

	for offset < h.Size {
		var err error
		var name string
		var size, headerSize int
		var flags *ID3v2FrameFlags

		switch h.Version {
		case ID3v2_2:
			name, size, headerSize, err = readID3v2_2FrameHeader(r)

		case ID3v2_3:
			name, size, headerSize, err = readID3v2_3FrameHeader(r)
			if err != nil {
				return nil, err
			}
			flags, err = readID3v2FrameFlags(r)
			headerSize += 2

		case ID3v2_4:
			name, size, headerSize, err = readID3v2_4FrameHeader(r)
			if err != nil {
				return nil, err
			}
			flags, err = readID3v2FrameFlags(r)
			headerSize += 2
		}

		if err != nil {
			return nil, err
		}

		// Avoid corrupted padding (see http://id3.org/Compliance%20Issues).
		if !validID3Frame(h.Version, name) {
			break
		}

		// FIXME: Do we still need this?
		// if size=0, we certainly are in a padding zone. ignore the rest of
		// the tags
		if size == 0 {
			break
		}

		offset += headerSize + size

		// Check this stuff out...
		if flags != nil && flags.DataLengthIndicator {
			_, err = read7BitChunkedInt(r, 4) // read 4
			if err != nil {
				return nil, err
			}
			size -= 4
		}

		if flags != nil && flags.Unsynchronisation {
			// FIXME: Implement this.
			continue
		}

		b, err := readBytes(r, size)
		if err != nil {
			return nil, err
		}

		// There can be multiple tag with the same name. Append a number to the
		// name if there is more than one.
		rawName := name
		if _, ok := result[rawName]; ok {
			for i := 0; ok; i++ {
				rawName = name + "_" + strconv.Itoa(i)
				_, ok = result[rawName]
			}
		}

		switch {
		case name == "TXXX" || name == "TXX":
			t, err := readTextWithDescrFrame(b, false, true) // no lang, but enc
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name[0] == 'T':
			txt, err := readTFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = txt

		case name == "UFID" || name == "UFI":
			t, err := readUFID(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name == "WXXX" || name == "WXX":
			t, err := readTextWithDescrFrame(b, false, false) // no lang, no enc
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name[0] == 'W':
			txt, err := readWFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = txt

		case name == "COMM" || name == "USLT":
			t, err := readTextWithDescrFrame(b, true, true) // both lang and enc
			if err != nil {
				return nil, err
			}
			result[rawName] = t

		case name == "APIC":
			p, err := readAPICFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = p

		case name == "PIC":
			p, err := readPICFrame(b)
			if err != nil {
				return nil, err
			}
			result[rawName] = p

		default:
			result[rawName] = b
		}
	}
	return result, nil
}

type unsynchroniser struct {
	io.Reader
	ff bool
}

// filter io.Reader which skip the Unsynchronisation bytes
func (r *unsynchroniser) Read(p []byte) (int, error) {
	b := make([]byte, 1)
	i := 0
	for i < len(p) {
		if n, err := r.Reader.Read(b); err != nil || n == 0 {
			return i, err
		}
		if r.ff && b[0] == 0x00 {
			r.ff = false
			continue
		}
		p[i] = b[0]
		i++
		r.ff = (b[0] == 0xFF)
	}
	return i, nil
}

// ReadID3v2Tags parses ID3v2.{2,3,4} tags from the io.ReadSeeker into a Metadata, returning
// non-nil error on failure.
func ReadID3v2Tags(r io.ReadSeeker) (Metadata, error) {
	h, err := readID3v2Header(r)
	if err != nil {
		return nil, err
	}

	var ur io.Reader
	ur = r
	if h.Unsynchronisation {
		ur = &unsynchroniser{Reader: r}
	}

	f, err := readID3v2Frames(ur, h)
	if err != nil {
		return nil, err
	}
	return metadataID3v2{header: h, frames: f}, nil
}
<|endoftext|>


<commit_before>package oraclebmc_sdk

import (
	"bytes"
	"encoding/json"
	"io"
	"time"
)

type CreateImageInput struct {
	CompartmentId string `json:"compartmentId"`
	DisplayName   string `json:"displayName"`
	InstanceId    string `json:"instanceId"`
}

type Image struct {
	Id                     string
	LifecycleState         string
	DisplayName            string
	TimeCreated            time.Time
	BaseImageId            string
	CompartmentId          string
	CreateImageAllowed     string
	OperatingSystemVersion string
}

func (createImageInput *CreateImageInput) asJSON() io.Reader {
	body, _ := json.Marshal(createImageInput)
	return bytes.NewBuffer(body)
}

func (image *Image) getId() string {
	return image.Id
}
func (image *Image) getState() string {
	return image.LifecycleState
}

func (image *Image) endpoint() string {
	return "images"
}

func (image *Image) validStates() []string {
	return []string{
		"PROVISIONING",
		"AVAILABLE",
		"DISABLED",
		"DELETED"}
}
<commit_msg>add image props<commit_after>package oraclebmc_sdk

import (
	"bytes"
	"encoding/json"
	"io"
	"time"
)

type CreateImageInput struct {
	CompartmentId string `json:"compartmentId"`
	DisplayName   string `json:"displayName"`
	InstanceId    string `json:"instanceId"`
}

type Image struct {
	BaseImageId            string
	CompartmentId          string
	CreateImageAllowed     bool
	DisplayName            string
	Id                     string
	LifecycleState         string
	operatingSystem        string
	OperatingSystemVersion string
	TimeCreated            time.Time
}

func (createImageInput *CreateImageInput) asJSON() io.Reader {
	body, _ := json.Marshal(createImageInput)
	return bytes.NewBuffer(body)
}

func (image *Image) getId() string {
	return image.Id
}
func (image *Image) getState() string {
	return image.LifecycleState
}

func (image *Image) endpoint() string {
	return "images"
}

func (image *Image) validStates() []string {
	return []string{
		"PROVISIONING",
		"AVAILABLE",
		"DISABLED",
		"DELETED"}
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"testing"
)

func TestLoadFromEnvironment(t *testing.T) {
	c := Context{}

	os.Setenv("PIPE_PORT", "1234")
	os.Setenv("PIPE_LOGLEVEL", "debug")
	os.Setenv("PIPE_LOGCOLORS", "true")
	os.Setenv("PIPE_MONGOURL", "server.example.com")
	os.Setenv("PIPE_ADMINNAME", "fake")
	os.Setenv("PIPE_ADMINKEY", "12345")
	os.Setenv("PIPE_POLL", "5000")
	os.Setenv("PIPE_IMAGE", "cloudpipe/runner-py2")
	os.Setenv("PIPE_DOCKERHOST", "tcp://1.2.3.4:4567/")
	os.Setenv("PIPE_DOCKERTLS", "true")
	os.Setenv("PIPE_CACERT", "/lockbox/ca.pem")
	os.Setenv("PIPE_CERT", "/lockbox/cert.pem")
	os.Setenv("PIPE_KEY", "/lockbox/key.pem")
	os.Setenv("PIPE_AUTHSERVICE", "https://auth")

	if err := c.Load(); err != nil {
		t.Errorf("Error loading configuration: %v", err)
	}

	if c.Port != 1234 {
		t.Errorf("Unexpected port: [%d]", c.Port)
	}

	if c.LogLevel != "debug" {
		t.Errorf("Unexpected log level: [%s]", c.LogLevel)
	}

	if !c.LogColors {
		t.Error("Expected log coloring to be enabled")
	}

	if c.MongoURL != "server.example.com" {
		t.Errorf("Unexpected MongoDB URL: [%s]", c.MongoURL)
	}

	if c.Poll != 5000 {
		t.Errorf("Unexpected polling interval: [%d]", c.Poll)
	}

	if c.DockerHost != "tcp://1.2.3.4:4567/" {
		t.Errorf("Unexpected docker host: [%s]", c.DockerHost)
	}

	if !c.DockerTLS {
		t.Errorf("Expected docker TLS to be enabled.")
	}

	if c.CACert != "/lockbox/ca.pem" {
		t.Errorf("Unexpected docker CA cert: [%s]", c.CACert)
	}

	if c.Cert != "/lockbox/cert.pem" {
		t.Errorf("Unexpected docker cert: [%s]", c.Cert)
	}

	if c.Key != "/lockbox/key.pem" {
		t.Errorf("Unexpected docker key: [%s]", c.Key)
	}

	if c.Image != "cloudpipe/runner-py2" {
		t.Errorf("Unexpected image: [%s]", c.Image)
	}

	if c.AdminName != "fake" {
		t.Errorf("Unexpected administrator name: [%s]", c.AdminName)
	}

	if c.AdminKey != "12345" {
		t.Errorf("Unexpected administrator API key: [%s]", c.AdminKey)
	}

	if c.Settings.AuthService != "https://auth" {
		t.Errorf("Unexpected authentication service URL: [%s]", c.AuthService)
	}
}

func TestDefaultValues(t *testing.T) {
	c := Context{}

	os.Setenv("PIPE_PORT", "")
	os.Setenv("PIPE_LOGLEVEL", "")
	os.Setenv("PIPE_LOGCOLORS", "")
	os.Setenv("PIPE_MONGOURL", "")
	os.Setenv("PIPE_ADMINNAME", "")
	os.Setenv("PIPE_ADMINKEY", "")
	os.Setenv("PIPE_POLL", "")
	os.Setenv("PIPE_DOCKERHOST", "")
	os.Setenv("DOCKER_HOST", "")
	os.Setenv("PIPE_DOCKERTLS", "")
	os.Setenv("PIPE_CACERT", "")
	os.Setenv("PIPE_CERT", "")
	os.Setenv("PIPE_KEY", "")
	os.Setenv("DOCKER_TLS_VERIFY", "")
	os.Setenv("DOCKER_CERT_PATH", "")
	os.Setenv("PIPE_IMAGE", "")
	os.Setenv("PIPE_AUTHSERVICE", "")

	if err := c.Load(); err != nil {
		t.Errorf("Error loading configuration: %v", err)
	}

	if c.Port != 8000 {
		t.Errorf("Unexpected port: [%d]", c.Port)
	}

	if c.LogLevel != "info" {
		t.Errorf("Unexpected logging level: [%s]", c.LogLevel)
	}

	if c.LogColors {
		t.Error("Expected logging colors to be disabled by default")
	}

	if c.MongoURL != "mongo" {
		t.Errorf("Unexpected MongoDB connection URL: [%s]", c.MongoURL)
	}

	if c.Poll != 500 {
		t.Errorf("Unexpected polling interval: [%d]", c.Poll)
	}

	if c.DockerHost != "unix:///var/run/docker.sock" {
		t.Errorf("Unexpected docker host: [%s]", c.DockerHost)
	}

	if c.DockerTLS {
		t.Errorf("Expected docker TLS to be disabled.")
	}

	if c.CACert != "/certificates/ca.pem" {
		t.Errorf("Unexpected docker CA cert: [%s]", c.CACert)
	}

	if c.Cert != "/certificates/cloudpipe-cert.pem" {
		t.Errorf("Unexpected docker cert: [%s]", c.Cert)
	}

	if c.Key != "/certificates/cloudpipe-key.pem" {
		t.Errorf("Unexpected docker key: [%s]", c.Key)
	}

	if c.Image != "cloudpipe/runner-py2" {
		t.Errorf("Unexpected default image: [%s]", c.Image)
	}

	if c.Settings.AuthService != "https://authstore:9001/v1" {
		t.Errorf("Unexpected default auth service: [%s]", c.AuthService)
	}
}

func TestUseDockerHost(t *testing.T) {
	os.Setenv("PIPE_DOCKERHOST", "")
	os.Setenv("DOCKER_HOST", "tcp://1.2.3.4:4567/")

	c := Context{}
	if err := c.Load(); err != nil {
		t.Errorf("Error loading configuration: %v", err)
	}

	if c.DockerHost != "tcp://1.2.3.4:4567/" {
		t.Errorf("Unexpected docker host: [%s]", c.DockerHost)
	}
}

func TestAddressString(t *testing.T) {
	c := Context{
		Settings: Settings{Port: 1234},
	}

	if c.ListenAddr() != ":1234" {
		t.Errorf("Unexpected listen address: %s", c.ListenAddr())
	}
}

func TestValidateLogLevel(t *testing.T) {
	c := Context{}

	os.Setenv("PIPE_LOGLEVEL", "Walrus")

	err := c.Load()
	if err == nil {
		t.Errorf("Expected an error when loading an invalid PIPE_LOG_LEVEL.")
	}
}
<commit_msg>Env var changes should be different from default<commit_after>package main

import (
	"os"
	"testing"
)

func TestLoadFromEnvironment(t *testing.T) {
	c := Context{}

	os.Setenv("PIPE_PORT", "1234")
	os.Setenv("PIPE_LOGLEVEL", "debug")
	os.Setenv("PIPE_LOGCOLORS", "true")
	os.Setenv("PIPE_MONGOURL", "server.example.com")
	os.Setenv("PIPE_ADMINNAME", "fake")
	os.Setenv("PIPE_ADMINKEY", "12345")
	os.Setenv("PIPE_POLL", "5000")
	os.Setenv("PIPE_IMAGE", "cloudpipe/runner-trial")
	os.Setenv("PIPE_DOCKERHOST", "tcp://1.2.3.4:4567/")
	os.Setenv("PIPE_DOCKERTLS", "true")
	os.Setenv("PIPE_CACERT", "/lockbox/ca.pem")
	os.Setenv("PIPE_CERT", "/lockbox/cert.pem")
	os.Setenv("PIPE_KEY", "/lockbox/key.pem")
	os.Setenv("PIPE_AUTHSERVICE", "https://auth")

	if err := c.Load(); err != nil {
		t.Errorf("Error loading configuration: %v", err)
	}

	if c.Port != 1234 {
		t.Errorf("Unexpected port: [%d]", c.Port)
	}

	if c.LogLevel != "debug" {
		t.Errorf("Unexpected log level: [%s]", c.LogLevel)
	}

	if !c.LogColors {
		t.Error("Expected log coloring to be enabled")
	}

	if c.MongoURL != "server.example.com" {
		t.Errorf("Unexpected MongoDB URL: [%s]", c.MongoURL)
	}

	if c.Poll != 5000 {
		t.Errorf("Unexpected polling interval: [%d]", c.Poll)
	}

	if c.DockerHost != "tcp://1.2.3.4:4567/" {
		t.Errorf("Unexpected docker host: [%s]", c.DockerHost)
	}

	if !c.DockerTLS {
		t.Errorf("Expected docker TLS to be enabled.")
	}

	if c.CACert != "/lockbox/ca.pem" {
		t.Errorf("Unexpected docker CA cert: [%s]", c.CACert)
	}

	if c.Cert != "/lockbox/cert.pem" {
		t.Errorf("Unexpected docker cert: [%s]", c.Cert)
	}

	if c.Key != "/lockbox/key.pem" {
		t.Errorf("Unexpected docker key: [%s]", c.Key)
	}

	if c.Image != "cloudpipe/runner-trial" {
		t.Errorf("Unexpected image: [%s]", c.Image)
	}

	if c.AdminName != "fake" {
		t.Errorf("Unexpected administrator name: [%s]", c.AdminName)
	}

	if c.AdminKey != "12345" {
		t.Errorf("Unexpected administrator API key: [%s]", c.AdminKey)
	}

	if c.Settings.AuthService != "https://auth" {
		t.Errorf("Unexpected authentication service URL: [%s]", c.AuthService)
	}
}

func TestDefaultValues(t *testing.T) {
	c := Context{}

	os.Setenv("PIPE_PORT", "")
	os.Setenv("PIPE_LOGLEVEL", "")
	os.Setenv("PIPE_LOGCOLORS", "")
	os.Setenv("PIPE_MONGOURL", "")
	os.Setenv("PIPE_ADMINNAME", "")
	os.Setenv("PIPE_ADMINKEY", "")
	os.Setenv("PIPE_POLL", "")
	os.Setenv("PIPE_DOCKERHOST", "")
	os.Setenv("DOCKER_HOST", "")
	os.Setenv("PIPE_DOCKERTLS", "")
	os.Setenv("PIPE_CACERT", "")
	os.Setenv("PIPE_CERT", "")
	os.Setenv("PIPE_KEY", "")
	os.Setenv("DOCKER_TLS_VERIFY", "")
	os.Setenv("DOCKER_CERT_PATH", "")
	os.Setenv("PIPE_IMAGE", "")
	os.Setenv("PIPE_AUTHSERVICE", "")

	if err := c.Load(); err != nil {
		t.Errorf("Error loading configuration: %v", err)
	}

	if c.Port != 8000 {
		t.Errorf("Unexpected port: [%d]", c.Port)
	}

	if c.LogLevel != "info" {
		t.Errorf("Unexpected logging level: [%s]", c.LogLevel)
	}

	if c.LogColors {
		t.Error("Expected logging colors to be disabled by default")
	}

	if c.MongoURL != "mongo" {
		t.Errorf("Unexpected MongoDB connection URL: [%s]", c.MongoURL)
	}

	if c.Poll != 500 {
		t.Errorf("Unexpected polling interval: [%d]", c.Poll)
	}

	if c.DockerHost != "unix:///var/run/docker.sock" {
		t.Errorf("Unexpected docker host: [%s]", c.DockerHost)
	}

	if c.DockerTLS {
		t.Errorf("Expected docker TLS to be disabled.")
	}

	if c.CACert != "/certificates/ca.pem" {
		t.Errorf("Unexpected docker CA cert: [%s]", c.CACert)
	}

	if c.Cert != "/certificates/cloudpipe-cert.pem" {
		t.Errorf("Unexpected docker cert: [%s]", c.Cert)
	}

	if c.Key != "/certificates/cloudpipe-key.pem" {
		t.Errorf("Unexpected docker key: [%s]", c.Key)
	}

	if c.Image != "cloudpipe/runner-py2" {
		t.Errorf("Unexpected default image: [%s]", c.Image)
	}

	if c.Settings.AuthService != "https://authstore:9001/v1" {
		t.Errorf("Unexpected default auth service: [%s]", c.AuthService)
	}
}

func TestUseDockerHost(t *testing.T) {
	os.Setenv("PIPE_DOCKERHOST", "")
	os.Setenv("DOCKER_HOST", "tcp://1.2.3.4:4567/")

	c := Context{}
	if err := c.Load(); err != nil {
		t.Errorf("Error loading configuration: %v", err)
	}

	if c.DockerHost != "tcp://1.2.3.4:4567/" {
		t.Errorf("Unexpected docker host: [%s]", c.DockerHost)
	}
}

func TestAddressString(t *testing.T) {
	c := Context{
		Settings: Settings{Port: 1234},
	}

	if c.ListenAddr() != ":1234" {
		t.Errorf("Unexpected listen address: %s", c.ListenAddr())
	}
}

func TestValidateLogLevel(t *testing.T) {
	c := Context{}

	os.Setenv("PIPE_LOGLEVEL", "Walrus")

	err := c.Load()
	if err == nil {
		t.Errorf("Expected an error when loading an invalid PIPE_LOG_LEVEL.")
	}
}
<|endoftext|>


<commit_before>package cmd

const Version = "3.20.0"
<commit_msg>version bump<commit_after>package cmd

const Version = "3.21.0"
<|endoftext|>


<commit_before>package taskrunner

import (
	"context"
	"encoding/json"
	"fmt"
	"path/filepath"
	"runtime"
	"time"

	hclog "github.com/hashicorp/go-hclog"
	plugin "github.com/hashicorp/go-plugin"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	"github.com/hashicorp/nomad/client/logmon"
	"github.com/hashicorp/nomad/helper/uuid"
	"github.com/hashicorp/nomad/nomad/structs"
	bstructs "github.com/hashicorp/nomad/plugins/base/structs"
	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
)

const (
	// logmonReattachKey is the HookData key where logmon's reattach config
	// is stored.
	logmonReattachKey = "reattach_config"
)

// logmonHook launches logmon and manages task logging
type logmonHook struct {
	// logmon is the handle to the log monitor process for the task.
	logmon             logmon.LogMon
	logmonPluginClient *plugin.Client

	config *logmonHookConfig

	logger hclog.Logger
}

type logmonHookConfig struct {
	logDir     string
	stdoutFifo string
	stderrFifo string
}

func newLogMonHook(cfg *logmonHookConfig, logger hclog.Logger) *logmonHook {
	hook := &logmonHook{
		config: cfg,
		logger: logger,
	}

	return hook
}

func newLogMonHookConfig(taskName, logDir string) *logmonHookConfig {
	cfg := &logmonHookConfig{
		logDir: logDir,
	}
	if runtime.GOOS == "windows" {
		id := uuid.Generate()[:8]
		cfg.stdoutFifo = fmt.Sprintf("//./pipe/%s-%s.stdout", taskName, id)
		cfg.stderrFifo = fmt.Sprintf("//./pipe/%s-%s.stderr", taskName, id)
	} else {
		cfg.stdoutFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stdout.fifo", taskName))
		cfg.stderrFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stderr.fifo", taskName))
	}
	return cfg
}

func (*logmonHook) Name() string {
	return "logmon"
}

func (h *logmonHook) launchLogMon(reattachConfig *plugin.ReattachConfig) error {
	l, c, err := logmon.LaunchLogMon(h.logger, reattachConfig)
	if err != nil {
		return err
	}

	h.logmon = l
	h.logmonPluginClient = c
	return nil
}

func reattachConfigFromHookData(data map[string]string) (*plugin.ReattachConfig, error) {
	if data == nil || data[logmonReattachKey] == "" {
		return nil, nil
	}

	var cfg pstructs.ReattachConfig
	err := json.Unmarshal([]byte(data[logmonReattachKey]), &cfg)
	if err != nil {
		return nil, err
	}

	return pstructs.ReattachConfigToGoPlugin(&cfg)
}

func (h *logmonHook) Prestart(ctx context.Context,
	req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {

	attempts := 0
	for {
		err := h.prestartOneLoop(ctx, req)
		if err == bstructs.ErrPluginShutdown {
			h.logger.Warn("logmon shutdown while making request", "error", err)

			if attempts > 3 {
				h.logger.Warn("logmon shutdown while making request; giving up", "attempts", attempts, "error", err)
				return err
			}

			// retry after killing process and ensure we start a new logmon process
			attempts++
			h.logger.Warn("logmon shutdown while making request; retrying", "attempts", attempts, "error", err)
			h.logmonPluginClient.Kill()
			time.Sleep(1 * time.Second)
			continue
		} else if err != nil {
			return err
		}

		rCfg := pstructs.ReattachConfigFromGoPlugin(h.logmonPluginClient.ReattachConfig())
		jsonCfg, err := json.Marshal(rCfg)
		if err != nil {
			return err
		}
		resp.State = map[string]string{logmonReattachKey: string(jsonCfg)}
		return nil
	}
}

func (h *logmonHook) prestartOneLoop(ctx context.Context, req *interfaces.TaskPrestartRequest) error {
	// attach to a running logmon if state indicates one
	if h.logmonPluginClient == nil {
		reattachConfig, err := reattachConfigFromHookData(req.PreviousState)
		if err != nil {
			h.logger.Error("failed to load reattach config", "error", err)
			return err
		}
		if reattachConfig != nil {
			if err := h.launchLogMon(reattachConfig); err != nil {
				h.logger.Warn("failed to reattach to logmon process", "error", err)
				// if we failed to launch logmon, try again below
			}
		}

	}

	// create a new client in initial starts, failed reattachment, or if we detect exits
	if h.logmonPluginClient == nil || h.logmonPluginClient.Exited() {
		if err := h.launchLogMon(nil); err != nil {
			// Retry errors launching logmon as logmon may have crashed on start and
			// subsequent attempts will start a new one.
			h.logger.Error("failed to launch logmon process", "error", err)
			return structs.NewRecoverableError(err, true)
		}
	}

	err := h.logmon.Start(&logmon.LogConfig{
		LogDir:        h.config.logDir,
		StdoutLogFile: fmt.Sprintf("%s.stdout", req.Task.Name),
		StderrLogFile: fmt.Sprintf("%s.stderr", req.Task.Name),
		StdoutFifo:    h.config.stdoutFifo,
		StderrFifo:    h.config.stderrFifo,
		MaxFiles:      req.Task.LogConfig.MaxFiles,
		MaxFileSizeMB: req.Task.LogConfig.MaxFileSizeMB,
	})
	if err != nil {
		h.logger.Error("failed to start logmon", "error", err)
		return err
	}

	return nil
}

func (h *logmonHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, _ *interfaces.TaskStopResponse) error {

	// It's possible that Stop was called without calling Prestart on agent
	// restarts. Attempt to reattach to an existing logmon.
	if h.logmon == nil || h.logmonPluginClient == nil {
		if err := h.reattach(req); err != nil {
			h.logger.Trace("error reattaching to logmon when stopping", "error", err)
		}
	}

	if h.logmon != nil {
		h.logmon.Stop()
	}
	if h.logmonPluginClient != nil {
		h.logmonPluginClient.Kill()
	}

	return nil
}

// reattach to a running logmon if possible. Will not start a new logmon.
func (h *logmonHook) reattach(req *interfaces.TaskStopRequest) error {
	reattachConfig, err := reattachConfigFromHookData(req.ExistingState)
	if err != nil {
		return err
	}

	// Give up if there's no reattach config
	if reattachConfig == nil {
		return nil
	}

	return h.launchLogMon(reattachConfig)
}
<commit_msg>retry grpc unavailable errors even if not shutting down<commit_after>package taskrunner

import (
	"context"
	"encoding/json"
	"fmt"
	"path/filepath"
	"runtime"
	"time"

	hclog "github.com/hashicorp/go-hclog"
	plugin "github.com/hashicorp/go-plugin"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	"github.com/hashicorp/nomad/client/logmon"
	"github.com/hashicorp/nomad/helper/uuid"
	"github.com/hashicorp/nomad/nomad/structs"
	bstructs "github.com/hashicorp/nomad/plugins/base/structs"
	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

const (
	// logmonReattachKey is the HookData key where logmon's reattach config
	// is stored.
	logmonReattachKey = "reattach_config"
)

// logmonHook launches logmon and manages task logging
type logmonHook struct {
	// logmon is the handle to the log monitor process for the task.
	logmon             logmon.LogMon
	logmonPluginClient *plugin.Client

	config *logmonHookConfig

	logger hclog.Logger
}

type logmonHookConfig struct {
	logDir     string
	stdoutFifo string
	stderrFifo string
}

func newLogMonHook(cfg *logmonHookConfig, logger hclog.Logger) *logmonHook {
	hook := &logmonHook{
		config: cfg,
		logger: logger,
	}

	return hook
}

func newLogMonHookConfig(taskName, logDir string) *logmonHookConfig {
	cfg := &logmonHookConfig{
		logDir: logDir,
	}
	if runtime.GOOS == "windows" {
		id := uuid.Generate()[:8]
		cfg.stdoutFifo = fmt.Sprintf("//./pipe/%s-%s.stdout", taskName, id)
		cfg.stderrFifo = fmt.Sprintf("//./pipe/%s-%s.stderr", taskName, id)
	} else {
		cfg.stdoutFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stdout.fifo", taskName))
		cfg.stderrFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stderr.fifo", taskName))
	}
	return cfg
}

func (*logmonHook) Name() string {
	return "logmon"
}

func (h *logmonHook) launchLogMon(reattachConfig *plugin.ReattachConfig) error {
	l, c, err := logmon.LaunchLogMon(h.logger, reattachConfig)
	if err != nil {
		return err
	}

	h.logmon = l
	h.logmonPluginClient = c
	return nil
}

func reattachConfigFromHookData(data map[string]string) (*plugin.ReattachConfig, error) {
	if data == nil || data[logmonReattachKey] == "" {
		return nil, nil
	}

	var cfg pstructs.ReattachConfig
	err := json.Unmarshal([]byte(data[logmonReattachKey]), &cfg)
	if err != nil {
		return nil, err
	}

	return pstructs.ReattachConfigToGoPlugin(&cfg)
}

func (h *logmonHook) Prestart(ctx context.Context,
	req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {

	attempts := 0
	for {
		err := h.prestartOneLoop(ctx, req)
		if err == bstructs.ErrPluginShutdown || grpc.Code(err) == codes.Unavailable {
			h.logger.Warn("logmon shutdown while making request", "error", err)

			if attempts > 3 {
				h.logger.Warn("logmon shutdown while making request; giving up", "attempts", attempts, "error", err)
				return err
			}

			// retry after killing process and ensure we start a new logmon process
			attempts++
			h.logger.Warn("logmon shutdown while making request; retrying", "attempts", attempts, "error", err)
			h.logmonPluginClient.Kill()
			time.Sleep(1 * time.Second)
			continue
		} else if err != nil {
			return err
		}

		rCfg := pstructs.ReattachConfigFromGoPlugin(h.logmonPluginClient.ReattachConfig())
		jsonCfg, err := json.Marshal(rCfg)
		if err != nil {
			return err
		}
		resp.State = map[string]string{logmonReattachKey: string(jsonCfg)}
		return nil
	}
}

func (h *logmonHook) prestartOneLoop(ctx context.Context, req *interfaces.TaskPrestartRequest) error {
	// attach to a running logmon if state indicates one
	if h.logmonPluginClient == nil {
		reattachConfig, err := reattachConfigFromHookData(req.PreviousState)
		if err != nil {
			h.logger.Error("failed to load reattach config", "error", err)
			return err
		}
		if reattachConfig != nil {
			if err := h.launchLogMon(reattachConfig); err != nil {
				h.logger.Warn("failed to reattach to logmon process", "error", err)
				// if we failed to launch logmon, try again below
			}
		}

	}

	// create a new client in initial starts, failed reattachment, or if we detect exits
	if h.logmonPluginClient == nil || h.logmonPluginClient.Exited() {
		if err := h.launchLogMon(nil); err != nil {
			// Retry errors launching logmon as logmon may have crashed on start and
			// subsequent attempts will start a new one.
			h.logger.Error("failed to launch logmon process", "error", err)
			return structs.NewRecoverableError(err, true)
		}
	}

	err := h.logmon.Start(&logmon.LogConfig{
		LogDir:        h.config.logDir,
		StdoutLogFile: fmt.Sprintf("%s.stdout", req.Task.Name),
		StderrLogFile: fmt.Sprintf("%s.stderr", req.Task.Name),
		StdoutFifo:    h.config.stdoutFifo,
		StderrFifo:    h.config.stderrFifo,
		MaxFiles:      req.Task.LogConfig.MaxFiles,
		MaxFileSizeMB: req.Task.LogConfig.MaxFileSizeMB,
	})
	if err != nil {
		h.logger.Error("failed to start logmon", "error", err)
		return err
	}

	return nil
}

func (h *logmonHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, _ *interfaces.TaskStopResponse) error {

	// It's possible that Stop was called without calling Prestart on agent
	// restarts. Attempt to reattach to an existing logmon.
	if h.logmon == nil || h.logmonPluginClient == nil {
		if err := h.reattach(req); err != nil {
			h.logger.Trace("error reattaching to logmon when stopping", "error", err)
		}
	}

	if h.logmon != nil {
		h.logmon.Stop()
	}
	if h.logmonPluginClient != nil {
		h.logmonPluginClient.Kill()
	}

	return nil
}

// reattach to a running logmon if possible. Will not start a new logmon.
func (h *logmonHook) reattach(req *interfaces.TaskStopRequest) error {
	reattachConfig, err := reattachConfigFromHookData(req.ExistingState)
	if err != nil {
		return err
	}

	// Give up if there's no reattach config
	if reattachConfig == nil {
		return nil
	}

	return h.launchLogMon(reattachConfig)
}
<|endoftext|>