<commit_before>// Package pulse provides operations for consuming mozilla pulse messages (see
// https://pulse.mozilla.org/).
//
// For users that are interested in publishing messages, or having lower level
// control of the amqp interactions with pulse, take a look at
// http://godoc.org/github.com/streadway/amqp.  This library is built on top of
// the amqp package.
//
// For a user that is simply interesting in consuming pulse messages without
// wishing to acquire a detailed understanding of how pulse.mozilla.org has
// been designed, or how AMQP 0.9.1 works, this client provides basic utility
// methods to get you started off quickly.
//
// Please note that parent package "github.com/petemoore/pulse-go" provides a
// very simple command line interface into this library too, which can be
// called directly from a shell, for example, so that the user requires no go
// programming expertise, and can directly write e.g. shell scripts that
// process pulse messages.
//
// To get started, we have created an example program which uses this library.
// The source code for this example is available at
// https://github.com/petemoore/pulse-go/blob/master/pulsesniffer/pulsesniffer.go.
// Afterwards, we will describe how it works. Do not worry if none of it makes
// sense now.  By the end of this overview it will all be explained.
//
//  // Package pulsesniffer provides a simple example program that listens to some
//  // real world pulse messages.
//  package main
//
//  import (
//  	"fmt"
//  	"github.com/petemoore/pulse-go/pulse"
//  	"github.com/streadway/amqp"
//  )
//
//  func main() {
//  	// Passing all empty strings:
//  	// empty user => use PULSE_USERNAME env var
//  	// empty password => use PULSE_PASSWORD env var
//  	// empty url => connect to production
//  	conn := pulse.NewConnection("", "", "")
//  	conn.Consume(
//  		"taskprocessing", // queue name
//  		func(message interface{}, delivery amqp.Delivery) { // callback function to pass messages to
//  			fmt.Println("Received from exchange " + delivery.Exchange + ":")
//  			fmt.Println(string(delivery.Body))
//  			fmt.Println("")
//  			delivery.Ack(false) // acknowledge message *after* processing
//  		},
//  		1,     // prefetch 1 message at a time
//  		false, // don't auto-acknowledge messages
//  		pulse.Bind( // routing key and exchange to get messages from
//  			"*.*.*.*.*.*.gaia.#",
//  			"exchange/taskcluster-queue/v1/task-defined"),
//  		pulse.Bind( // another routing key and exchange to get messages from
//  			"*.*.*.*.*.aws-provisioner.#",
//  			"exchange/taskcluster-queue/v1/task-running"))
//  	conn.Consume( // a second workflow to manage concurrently
//  		"", // empty name implies anonymous queue
//  		func(message interface{}, delivery amqp.Delivery) { // simpler callback than before
//  			fmt.Println("Buildbot message received")
//  			fmt.Println("")
//  		},
//  		1,    // prefetch
//  		true, // auto acknowledge, so no need to call delivery.Ack
//  		pulse.Bind( // routing key and exchange to get messages from
//  			"#", // get *all* normalized buildbot messages
//  			"exchange/build/normalized"))
//  	// wait forever
//  	forever := make(chan bool)
//  	<-forever
//  }
// The first thing we need to do is provide connection details for connecting
// to the pulse server, which we do like this:
//
//  conn := pulse.NewConnection("", "", "")
//
// In this example, the provided strings (username, password, url) have all
// been left empty. This is because by default, if you provide no username or
// password, the NewConnection function will inspect environment variables
// PULSE_USERNAME and PULSE_PASSWORD, and an empty url will trigger the library
// to use the current production url. Another example call could be:
//
//  conn := pulse.NewConnection("guest", "guest", "amqp://localhost:5672/")
//
// Typically we would set the username and password credentials via environment
// variables to avoid hardcoding them in the go code.  For more details about
// managing the username, password and amqp url, see the documentation for the
// NewConnection function.
//
// A call to NewConnection does not actually create a connection to the pulse
// server, it simply prepares the data that will be needed when we finally make
// the connection.  Users and passwords can be created by going to the Pulse
// Guardian (https://pulse.mozilla.org) and registering an account.
//
// You will see in the code above, that after creating a connection, there is
// only one more method we call - Consume - which we use for processing
// messages. This is the heart of the pulse library, and where all of the
// action happens.
//
// In pulse, all messages are delivered to "topic exchanges" and the way to
// receive these messages is to request the ones you are interested in are
// copied onto a queue you can read from, and then to read them from the queue.
// This is called binding. To bind messages from an exchange to a queue, you
// specify the name of the exchange you want to receive messages from, and a
// matching criteria to define the ones you want. The matching process is
// handled by routing keys, which will now be explained.
//
// Each message that arrives on an exchange has a "routing key" signature.  The
// routing key comprises of several fields. For an example, see:
// http://docs.taskcluster.net/queue/exchanges/#taskDefined.  The fields are
// delimited by dots, and therefore the routing key of a message is represented
// as a '.' delimited string.  In order to select the messages on an exchange
// that you wish to receive, you specify a matching routing key.  For each
// field of the routing key, you can either match against a specific value, or
// match all entries with the '*' wildcard.  Above, we specified the following
// routing key and exchange:
//
//  		pulse.Bind( // routing key and exchange to get messages from
//  		"*.*.*.*.*.*.gaia.#",
//  		"exchange/taskcluster-queue/v1/task-defined"),
//
// This would match all messages on the exchange
// "exchange/taskcluster-queue/v1/task-defined" which have a workerType of
// "gaia" (see the taskDefined link above).  Notice also the '#' at the end of
// the string. This means "match all remaining fields" and can be used to match
// whatever comes after.
//
// To see the list of available exchanges on pulse, visit
// https://wiki.mozilla.org/Auto-tools/Projects/Pulse/Exchanges.
//
// After deciding which exchanges you are interested in, you need a queue to
// have them copied onto. This is also handled by the Consume method, with the
// first argument being the name of the queue to use.  You will notice above
// there are two types of queues we create: named queues, and unnamed queues:
//
//  	conn.Consume(
//  		"taskprocessing", // queue name
//
//  	conn.Consume( // a second workflow to manage concurrently
//  		"", // empty name implies anonymous queue
//
// To understand the difference, first we need to explain the different
// scenarios in which you might want to use them.
//
// Scenario 1) You have one client reading from the queue, and when you
// disconnect, you don't want your queue to receive any more messages
//
// Scenario 2) you have multiple clients that want to feed from the same queue
// (e.g.  when multiple workers can perform the same task, and whichever one
// pops the message off the queue first should process it)
//
// Scenario 3) you only have a single client reading from the queue, but if you
// go offline (crash, network interrupts etc) then you want pulse to keep
// updating your queue so your missed messages are there when you get back.
//
// In scenario 1 above, your client only uses the queue for the scope of the
// connection, and as soon as it disconnects, does not require the queue any
// further. In this case, an unnamed queue can be created, by passing "" as the
// queue name. When the connection closes, the AMQP server will automatically
// delete the queue.
//
// In scenarios 2 it is useful to have a friendly name for the queue that can
// be shared by all the clients using it.  The queue also should not be deleted
// when one client disconnects, it needs to live indefinitely. By providing a
// name for the queue, this signifies to the pulse library, that the queue
// should persist after a disconnect, and pulse should continue to populate the
// queue, even if no pulse clients are connected to consume the messages.
// Please note eventually the Pulse Guardian will delete your queue if you
// leave it collecting messages without consuming them.
//
// Scenario 3 is essentially the same as scenario 2 but with one consumer only.
// Again, a named queue is required.
//
// So, we're nearly done now. We now have a means to consume messages, by
// calling the Consume method, and specifying a queue name, some bindings of
// exchanges and routing keys, but how to actually process messages arriving on
// the queue?
//
// You will notice the Consume method takes a callback function. This can be an
// inline function, or point to any available function in your go code. You
// simply need to have a function that accepts an amqp.Delivery input, and pass
// it into the Consume method. Above, we did it like this:
//
//  		func(message interface{}, delivery amqp.Delivery) { // callback function to pass messages to
//  			fmt.Println("Received from exchange " + delivery.Exchange + ":")
//  			fmt.Println(string(delivery.Body))
//  			fmt.Println("")
//  			delivery.Ack(false) // acknowledge message *after* processing
//  		},
//
// The two parameters of the callback function we have created are the message
// object, and the delivery object. The message object is the pulse message,
// but unmarshaled into an interface{}. Since the pulse messages are all json
// messages, the pulse library unmarshals it and give you back a go object with
// its contents. Please note if you require that the json is unmarshaled into
// something more specific than interface{}, such as a custom class, this is
// possible, and will be explained in the next paragraph.  The other parameter,
// the delivery object, is an underlying amqp library type, which gives you
// access to some meta data for the message.  Please see
// http://godoc.org/github.com/streadway/amqp#Delivery for more information.
// Among other things, it provides you with delivery.Body, which is the raw
// json of the message. You can therefore choose if you want to process the raw
// json or the unmarshaled json in your callback method.
//
// You recall above that to describe the binding from an exchange to a queue
// with a given routing key, we specified pulse.Bind(routingKey, exchange) as a
// parameter of the Consume method. pulse.Bind(...) returns an object of type ,
// where http://godoc.org/github.com/petemoore/pulse-go/pulse#Binding is an
// interface. If you wish to unmarshal your json into something other than an
// interface{}, take a look at the Binding interface documentation. Instead of
// calling pulse.Bind(...) you can provide your own Binding interface
// implementation which can enable custom handling of exchange names, routing
// keys, and unmarshaling of objects. The taskcluster go client relies heavily
// on this, for example. See
// https://github.com/petemoore/taskcluster-client-go/tree/master/tctasksniffer/sniffer.go
// for inspiration.
//
// In this example above, we simply output the information we receive, and then
// acknowledge receipt of the message. But why do we need to do this? To explain,
// take a look at the remaining parameters to Consume that we pass in. There
// are two more we have not discussed yet: they are the prefetch size (how many
// messages to fetch at once), and a bool to say whether to auto-acknowledge
// messages or not.
//
//  		1,     // prefetch 1 message at a time
//  		false, // don't auto-acknowledge messages
//
// When you acknowledge a message, it gets popped off the queue. If you don't
// auto-acknowledge, and also don't manually acknowledge, your queue is going
// to grow until it gets deleted by Pulse Guardian, so better to acknowledge
// those messages!  Auto-acknowledge happens when you receive the message; if
// you crash after receiving it but before processing it, you may have a
// problem. If it is important not to lose messages in such a scenario, you can
// acknowledge manually *after* processing the message. See above:
//
//  			delivery.Ack(false) // acknowledge message *after* processing
//
// This is "more work" for you to do, but guarantees that you don't lose
// messages. To handle situation of crashing after processing, but before
// acknowledging, having an idempotent message processing function (the
// callback) should help avoid the problem of processing a message twice.
//
// Please note the Consume method will take care of connecting to the pulse
// server (if no connection has yet been established), creating an AMQP
// channel, creating or connecting to an existing queue, binding it to all the
// exchanges and routing keys that you specify, and spawning a dedicated go
// routine to process the messages from this queue and feed them back to the
// callback method you provide.
//
// The client is implemented in such a way that a new AMQP channel is created
// for each queue that you consume, and that a separate go routine handles
// calling the callback function you specify. This means you can take advantage
// of go's built in concurrency support, and call the Consume method as many
// times as you wish.
//
// The aim of this library is to shield users from this lower-level resource
// management, and provide a simple interface in order to quickly and easily
// develop components that can interact with pulse.
package pulse
<commit_msg>text corrections<commit_after>// Package pulse provides operations for consuming mozilla pulse messages (see
// https://pulse.mozilla.org/).
//
// For users that are interested in publishing messages, or having lower level
// control of the amqp interactions with pulse, take a look at
// http://godoc.org/github.com/streadway/amqp.  This library is built on top of
// the amqp package.
//
// For a user that is simply interesting in consuming pulse messages without
// wishing to acquire a detailed understanding of how pulse.mozilla.org has
// been designed, or how AMQP 0.9.1 works, this client provides basic utility
// methods to get you started off quickly.
//
// Please note that parent package "github.com/petemoore/pulse-go" provides a
// very simple command line interface into this library too, which can be
// called directly from a shell, for example, so that the user requires no go
// programming expertise, and can directly write e.g. shell scripts that
// process pulse messages.
//
// To get started, we have created an example program which uses this library.
// The source code for this example is available at
// https://github.com/petemoore/pulse-go/blob/master/pulsesniffer/pulsesniffer.go.
// Afterwards, we will describe how it works. Do not worry if none of it makes
// sense now.  By the end of this overview it will all be explained.
//
//  // Package pulsesniffer provides a simple example program that listens to some
//  // real world pulse messages.
//  package main
//
//  import (
//  	"fmt"
//  	"github.com/petemoore/pulse-go/pulse"
//  	"github.com/streadway/amqp"
//  )
//
//  func main() {
//  	// Passing all empty strings:
//  	// empty user => use PULSE_USERNAME env var
//  	// empty password => use PULSE_PASSWORD env var
//  	// empty url => connect to production
//  	conn := pulse.NewConnection("", "", "")
//  	conn.Consume(
//  		"taskprocessing", // queue name
//  		func(message interface{}, delivery amqp.Delivery) { // callback function to pass messages to
//  			fmt.Println("Received from exchange " + delivery.Exchange + ":")
//  			fmt.Println(string(delivery.Body))
//  			fmt.Println("")
//  			delivery.Ack(false) // acknowledge message *after* processing
//  		},
//  		1,     // prefetch 1 message at a time
//  		false, // don't auto-acknowledge messages
//  		pulse.Bind( // routing key and exchange to get messages from
//  			"*.*.*.*.*.*.gaia.#",
//  			"exchange/taskcluster-queue/v1/task-defined"),
//  		pulse.Bind( // another routing key and exchange to get messages from
//  			"*.*.*.*.*.aws-provisioner.#",
//  			"exchange/taskcluster-queue/v1/task-running"))
//  	conn.Consume( // a second workflow to manage concurrently
//  		"", // empty name implies anonymous queue
//  		func(message interface{}, delivery amqp.Delivery) { // simpler callback than before
//  			fmt.Println("Buildbot message received")
//  			fmt.Println("")
//  		},
//  		1,    // prefetch
//  		true, // auto acknowledge, so no need to call delivery.Ack
//  		pulse.Bind( // routing key and exchange to get messages from
//  			"#", // get *all* normalized buildbot messages
//  			"exchange/build/normalized"))
//  	// wait forever
//  	forever := make(chan bool)
//  	<-forever
//  }
// The first thing we need to do is provide connection details for connecting
// to the pulse server, which we do like this:
//
//  conn := pulse.NewConnection("", "", "")
//
// In this example, the provided strings (username, password, url) have all
// been left empty. This is because by default, if you provide no username or
// password, the NewConnection function will inspect environment variables
// PULSE_USERNAME and PULSE_PASSWORD, and an empty url will trigger the library
// to use the current production url. Another example call could be:
//
//  conn := pulse.NewConnection("guest", "guest", "amqp://localhost:5672/")
//
// Typically we would set the username and password credentials via environment
// variables to avoid hardcoding them in the go code.  For more details about
// managing the username, password and amqp url, see the documentation for the
// NewConnection function.
//
// A call to NewConnection does not actually create a connection to the pulse
// server, it simply prepares the data that will be needed when we finally make
// the connection.  Users and passwords can be created by going to the Pulse
// Guardian (https://pulse.mozilla.org) and registering an account.
//
// You will see in the code above, that after creating a connection, there is
// only one more method we call - Consume - which we use for processing
// messages. This is the heart of the pulse library, and where all of the
// action happens.
//
// In pulse, all messages are delivered to "topic exchanges" and the way to
// receive these messages is to request the ones you are interested in are
// copied onto a queue you can read from, and then to read them from the queue.
// This is called binding. To bind messages from an exchange to a queue, you
// specify the name of the exchange you want to receive messages from, and a
// matching criteria to define the ones you want. The matching process is
// handled by routing keys, which will now be explained.
//
// Each message that arrives on an exchange has a "routing key" signature.  The
// routing key comprises of several fields. For an example, see:
// http://docs.taskcluster.net/queue/exchanges/#taskDefined.  The fields are
// delimited by dots, and therefore the routing key of a message is represented
// as a '.' delimited string.  In order to select the messages on an exchange
// that you wish to receive, you specify a matching routing key.  For each
// field of the routing key, you can either match against a specific value, or
// match all entries with the '*' wildcard.  Above, we specified the following
// routing key and exchange:
//
//  		pulse.Bind( // routing key and exchange to get messages from
//  		"*.*.*.*.*.*.gaia.#",
//  		"exchange/taskcluster-queue/v1/task-defined"),
//
// This would match all messages on the exchange
// "exchange/taskcluster-queue/v1/task-defined" which have a workerType of
// "gaia" (see the taskDefined link above).  Notice also the '#' at the end of
// the string. This means "match all remaining fields" and can be used to match
// whatever comes after.
//
// To see the list of available exchanges on pulse, visit
// https://wiki.mozilla.org/Auto-tools/Projects/Pulse/Exchanges.
//
// After deciding which exchanges you are interested in, you need a queue to
// have them copied onto. This is also handled by the Consume method, with the
// first argument being the name of the queue to use.  You will notice above
// there are two types of queues we create: named queues, and unnamed queues:
//
//  	conn.Consume(
//  		"taskprocessing", // queue name
//
//  	conn.Consume( // a second workflow to manage concurrently
//  		"", // empty name implies anonymous queue
//
// To understand the difference, first we need to explain the different
// scenarios in which you might want to use them.
//
// Scenario 1) You have one client reading from the queue, and when you
// disconnect, you don't want your queue to receive any more messages
//
// Scenario 2) you have multiple clients that want to feed from the same queue
// (e.g.  when multiple workers can perform the same task, and whichever one
// pops the message off the queue first should process it)
//
// Scenario 3) you only have a single client reading from the queue, but if you
// go offline (crash, network interrupts etc) then you want pulse to keep
// updating your queue so your missed messages are there when you get back.
//
// In scenario 1 above, your client only uses the queue for the scope of the
// connection, and as soon as it disconnects, does not require the queue any
// further. In this case, an unnamed queue can be created, by passing "" as the
// queue name. When the connection closes, the AMQP server will automatically
// delete the queue.
//
// In scenarios 2 it is useful to have a friendly name for the queue that can
// be shared by all the clients using it.  The queue also should not be deleted
// when one client disconnects, it needs to live indefinitely. By providing a
// name for the queue, this signifies to the pulse library, that the queue
// should persist after a disconnect, and pulse should continue to populate the
// queue, even if no pulse clients are connected to consume the messages.
// Please note eventually the Pulse Guardian will delete your queue if you
// leave it collecting messages without consuming them.
//
// Scenario 3 is essentially the same as scenario 2 but with one consumer only.
// Again, a named queue is required.
//
// So, we're nearly done now. We now have a means to consume messages, by
// calling the Consume method, and specifying a queue name, some bindings of
// exchanges and routing keys, but how to actually process messages arriving on
// the queue?
//
// You will notice the Consume method takes a callback function. This can be an
// inline function, or point to any available function in your go code. You
// simply need to have a function that accepts an amqp.Delivery input, and pass
// it into the Consume method. Above, we did it like this:
//
//  		func(message interface{}, delivery amqp.Delivery) { // callback function to pass messages to
//  			fmt.Println("Received from exchange " + delivery.Exchange + ":")
//  			fmt.Println(string(delivery.Body))
//  			fmt.Println("")
//  			delivery.Ack(false) // acknowledge message *after* processing
//  		},
//
// The two parameters of the callback function we have created are the message
// object, and the delivery object. The message object is the pulse message,
// but unmarshaled into an interface{}. Since the pulse messages are all json
// messages, the pulse library unmarshals it and give you back a go object with
// its contents. Please note if you require that the json is unmarshaled into
// something more specific than interface{}, such as a custom class, this is
// possible, and will be explained in the next paragraph.  The other parameter,
// the delivery object, is an underlying amqp library type, which gives you
// access to some meta data for the message.  Please see
// http://godoc.org/github.com/streadway/amqp#Delivery for more information.
// Among other things, it provides you with delivery.Body, which is the raw
// json of the message. You can therefore choose if you want to process the raw
// json or the unmarshaled json in your callback method.
//
// You recall above that to describe the binding from an exchange to a queue
// with a given routing key, we specified pulse.Bind(routingKey, exchange) as a
// parameter of the Consume method. pulse.Bind(routingKey, exchange) returns an
// object of type Binding, where Binding is an interface.  If you wish to
// unmarshal your json into something other than an interface{}, take a look at
// the Binding interface documentation
// (http://godoc.org/github.com/petemoore/pulse-go/pulse#Binding). Instead of
// calling pulse.Bind(routingKey, exchange) you can provide your own Binding
// interface implementation which can enable custom handling of exchange names,
// routing keys, and unmarshaling of objects. The taskcluster go client relies
// heavily on this, for example. See
// https://github.com/petemoore/taskcluster-client-go/tree/master/tctasksniffer/sniffer.go
// for inspiration.
//
// In this example above, we simply output the information we receive, and then
// acknowledge receipt of the message. But why do we need to do this? To explain,
// take a look at the remaining parameters to Consume that we pass in. There
// are two more we have not discussed yet: they are the prefetch size (how many
// messages to fetch at once), and a bool to say whether to auto-acknowledge
// messages or not.
//
//  		1,     // prefetch 1 message at a time
//  		false, // don't auto-acknowledge messages
//
// When you acknowledge a message, it gets popped off the queue. If you don't
// auto-acknowledge, and also don't manually acknowledge, your queue is going
// to grow until it gets deleted by Pulse Guardian, so better to acknowledge
// those messages!  Auto-acknowledge happens when you receive the message; if
// you crash after receiving it but before processing it, you may have a
// problem. If it is important not to lose messages in such a scenario, you can
// acknowledge manually *after* processing the message. See above:
//
//  			delivery.Ack(false) // acknowledge message *after* processing
//
// This is "more work" for you to do, but guarantees that you don't lose
// messages. To handle situation of crashing after processing, but before
// acknowledging, having an idempotent message processing function (the
// callback) should help avoid the problem of processing a message twice.
//
// Please note the Consume method will take care of connecting to the pulse
// server (if no connection has yet been established), creating an AMQP
// channel, creating or connecting to an existing queue, binding it to all the
// exchanges and routing keys that you specify, and spawning a dedicated go
// routine to process the messages from this queue and feed them back to the
// callback method you provide.
//
// The client is implemented in such a way that a new AMQP channel is created
// for each queue that you consume, and that a separate go routine handles
// calling the callback function you specify. This means you can take advantage
// of go's built in concurrency support, and call the Consume method as many
// times as you wish.
//
// The aim of this library is to shield users from this lower-level resource
// management, and provide a simple interface in order to quickly and easily
// develop components that can interact with pulse.
package pulse
<|endoftext|>


<commit_before>package service

import (
	"fmt"
	"reflect"

	"github.com/Rugvip/bullettime/db"
	"github.com/Rugvip/bullettime/types"
)

type Room struct {
	id types.RoomId
}

func GetRoom(id types.RoomId) (Room, error) {
	if err := db.RoomExists(id); err != nil {
		return Room{}, err
	}
	return Room{id: id}, nil
}

func CreateRoom(hostname string, creator User, desc *types.RoomDescription) (types.RoomId, *types.Alias, error) {
	var alias *types.Alias
	if desc.Alias != nil {
		a := types.NewAlias(*desc.Alias, hostname)
		alias = &a
	}
	id, err := db.CreateRoom(hostname, alias)
	if err != nil {
		return types.RoomId{}, nil, err
	}
	userId := creator.Id()
	_, err = db.SetRoomState(id, userId, &types.CreateEventContent{userId}, "")
	if err != nil {
		return types.RoomId{}, nil, err
	}
	profile, err := creator.GetProfile()
	if err != nil {
		return types.RoomId{}, nil, err
	}
	membership := &types.MembershipEventContent{&profile, types.MembershipMember}
	_, err = db.SetRoomState(id, userId, membership, userId.String())
	if err != nil {
		return types.RoomId{}, nil, err
	}
	_, err = db.SetRoomState(id, userId, types.DefaultPowerLevels(userId), "")
	if err != nil {
		return types.RoomId{}, nil, err
	}
	joinRuleContent := types.JoinRulesEventContent{desc.Visibility.ToJoinRule()}
	_, err = db.SetRoomState(id, userId, &joinRuleContent, "")
	if err != nil {
		return types.RoomId{}, nil, err
	}
	if alias != nil {
		_, err = db.SetRoomState(id, userId, &types.AliasesEventContent{[]types.Alias{*alias}}, "")
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	if desc.Name != nil {
		_, err = db.SetRoomState(id, userId, &types.NameEventContent{*desc.Name}, "")
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	if desc.Topic != nil {
		_, err = db.SetRoomState(id, userId, &types.TopicEventContent{*desc.Topic}, "")
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	for _, invited := range desc.Invited {
		membership := types.MembershipEventContent{nil, types.MembershipInvited}
		_, err = db.SetRoomState(id, userId, &membership, invited.String())
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	return id, alias, nil
}

func (r Room) Id() types.RoomId {
	return r.id
}

func (r Room) AddEvent(user User, content types.GenericContent) (*types.Event, error) {
	return nil, nil
}

func (r Room) SetState(user User, content types.TypedContent, stateKey string) (*types.State, error) {
	userIdStateKey, err := types.ParseUserId(stateKey)
	isUserIdStateKey := err == nil

	eventType := content.EventType()
	switch eventType {
	case types.EventTypeName:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypeTopic:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypeJoinRules:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypePowerLevels:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypeCreate:
		return nil, types.ForbiddenError("cannot set state " + eventType)

	case types.EventTypeAliases:
		return nil, types.ForbiddenError("cannot set state " + eventType)

	case types.EventTypeMembership:
		membership, ok := content.(*types.MembershipEventContent)
		if !ok || membership == nil {
			panic("expected membership event content, got " + reflect.TypeOf(content).String())
		}
		if !isUserIdStateKey {
			return nil, types.ForbiddenError("state key must be a user id for state " + eventType)
		}
		return r.doMembershipChange(user, userIdStateKey, membership)
	}
	if isUserIdStateKey && userIdStateKey != user.Id() {
		return nil, types.ForbiddenError("cannot set the state of another user")
	}
	return nil, nil
}

func (r Room) doMembershipChange(changeBy User, userId types.UserId, membership *types.MembershipEventContent) (*types.State, error) {
	currentMembership, err := r.UserMembership(userId)
	if err != nil {
		return nil, err
	}
	if currentMembership == membership.Membership {
		return nil, types.ForbiddenError("membership change was a no-op")
	}
	membership.UserProfile = nil

	switch membership.Membership {
	case types.MembershipNone:
		if currentMembership != types.MembershipBanned {
			return nil, types.BadJsonError("invalid or missing membership in membership change")
		}
		err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
			return pl.Ban
		})
		if err != nil {
			return nil, err
		}
		if userId == changeBy.Id() {
			return nil, types.ForbiddenError("cannot remove a ban from self")
		}

	case types.MembershipInvited:
		if currentMembership != types.MembershipNone {
			return nil, types.ForbiddenError("could not invite user to room, already have membership '" + currentMembership.String() + "'")
		}
		ok, err := r.AllowsJoinRule(types.JoinRuleInvite)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, types.ForbiddenError("room does not allow join method: " + types.JoinRuleInvite.String())
		}
		err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
			return pl.Invite
		})
		if err != nil {
			return nil, err
		}

	case types.MembershipMember:
		if userId != changeBy.Id() {
			return nil, types.ForbiddenError("cannot force other users to join the room")
		}
		profile, err := changeBy.GetProfile()
		if err != nil {
			return nil, err
		}
		membership.UserProfile = &profile

	case types.MembershipKnocking:
		if userId != changeBy.Id() {
			return nil, types.ForbiddenError("cannot force other users to knock")
		}
		if currentMembership != types.MembershipNone {
			return nil, types.ForbiddenError("could not knock on room, already have membership '" + currentMembership.String() + "'")
		}
		ok, err := r.AllowsJoinRule(types.JoinRuleKnock)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, types.ForbiddenError("room does not allow join method: " + types.JoinRuleKnock.String())
		}

	case types.MembershipLeaving:
		if currentMembership == types.MembershipNone {
			return nil, types.ForbiddenError("tried to leave a room without current membership")
		}
		if currentMembership == types.MembershipBanned {
			return nil, types.ForbiddenError("tried to leave room with current membership '" + types.MembershipBanned.String() + "'")
		}
		if userId != changeBy.Id() {
			err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
				return pl.Kick
			})
			if err != nil {
				return nil, err
			}
		}

	case types.MembershipBanned:
		if userId == changeBy.Id() {
			return nil, types.ForbiddenError("cannot ban self")
		}
		err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
			return pl.Ban
		})
		if err != nil {
			return nil, err
		}
	}
	return db.SetRoomState(r.Id(), changeBy.Id(), membership, userId.String())
}

func (r Room) TestPowerLevel(userId types.UserId, powerLevelFunc func(*types.PowerLevelsEventContent) int) error {
	powerLevels, err := r.PowerLevels()
	if err != nil {
		return err
	}
	userPowerLevel, err := r.UserPowerLevel(userId)
	if err != nil {
		return err
	}
	requiredPowerLevel := powerLevelFunc(powerLevels)
	if userPowerLevel < requiredPowerLevel {
		msg := fmt.Sprintf("not enough power level to perform action (%d < %d)", userPowerLevel, requiredPowerLevel)
		return types.ForbiddenError(msg)
	}
	return nil
}

func (r Room) UserMembership(userId types.UserId) (types.Membership, error) {
	state, err := db.GetRoomState(r.Id(), types.EventTypeMembership, userId.String())
	if err != nil {
		return types.MembershipNone, err
	}
	if state == nil {
		return types.MembershipNone, nil
	}
	membership, ok := state.Content.(*types.MembershipEventContent)
	if !ok {
		panic("invalid membership content, was " + reflect.TypeOf(state.Content).String())
	}
	return membership.Membership, nil
}

func (r Room) AllowsJoinRule(joinRule types.JoinRule) (bool, error) {
	state, err := db.GetRoomState(r.Id(), types.EventTypeJoinRules, "")
	if err != nil {
		return false, err
	}
	if state == nil {
		panic("room power levels are invalid or missing: " + r.Id().String())
	}
	joinRules, ok := state.Content.(*types.JoinRulesEventContent)
	if !ok {
		panic("invalid join rule content, was " + reflect.TypeOf(state.Content).String())
	}
	if joinRules.JoinRule != joinRule {
		return false, types.ForbiddenError("room does not allow join rule: " + joinRule.String())
	}
	return true, nil
}

func (r Room) PowerLevels() (*types.PowerLevelsEventContent, error) {
	state, err := db.GetRoomState(r.Id(), types.EventTypePowerLevels, "")
	if err != nil {
		return nil, err
	}
	if state == nil {
		panic("room power levels are invalid or missing: " + r.Id().String())
	}
	powerLevels, ok := state.Content.(*types.PowerLevelsEventContent)
	if !ok {
		panic("invalid power level content, was " + reflect.TypeOf(state.Content).String())
	}
	return powerLevels, nil
}

func (r Room) UserPowerLevel(userId types.UserId) (int, error) {
	powerLevels, err := r.PowerLevels()
	if err != nil {
		return 0, err
	}
	if userLevel, ok := powerLevels.Users[userId]; ok {
		return userLevel, nil
	}
	return powerLevels.UserDefault, nil
}

func (r Room) EventPowerLevel(eventType string) (int, error) {
	powerLevels, err := r.PowerLevels()
	if err != nil {
		return 0, err
	}
	if eventLevel, ok := powerLevels.Events[eventType]; ok {
		return eventLevel, nil
	}
	return powerLevels.EventDefault, nil
}
<commit_msg>service/rooms: renamed AddEvent to AddMessage and updated signature<commit_after>package service

import (
	"fmt"
	"reflect"

	"github.com/Rugvip/bullettime/db"
	"github.com/Rugvip/bullettime/types"
)

type Room struct {
	id types.RoomId
}

func GetRoom(id types.RoomId) (Room, error) {
	if err := db.RoomExists(id); err != nil {
		return Room{}, err
	}
	return Room{id: id}, nil
}

func CreateRoom(hostname string, creator User, desc *types.RoomDescription) (types.RoomId, *types.Alias, error) {
	var alias *types.Alias
	if desc.Alias != nil {
		a := types.NewAlias(*desc.Alias, hostname)
		alias = &a
	}
	id, err := db.CreateRoom(hostname, alias)
	if err != nil {
		return types.RoomId{}, nil, err
	}
	userId := creator.Id()
	_, err = db.SetRoomState(id, userId, &types.CreateEventContent{userId}, "")
	if err != nil {
		return types.RoomId{}, nil, err
	}
	profile, err := creator.GetProfile()
	if err != nil {
		return types.RoomId{}, nil, err
	}
	membership := &types.MembershipEventContent{&profile, types.MembershipMember}
	_, err = db.SetRoomState(id, userId, membership, userId.String())
	if err != nil {
		return types.RoomId{}, nil, err
	}
	_, err = db.SetRoomState(id, userId, types.DefaultPowerLevels(userId), "")
	if err != nil {
		return types.RoomId{}, nil, err
	}
	joinRuleContent := types.JoinRulesEventContent{desc.Visibility.ToJoinRule()}
	_, err = db.SetRoomState(id, userId, &joinRuleContent, "")
	if err != nil {
		return types.RoomId{}, nil, err
	}
	if alias != nil {
		_, err = db.SetRoomState(id, userId, &types.AliasesEventContent{[]types.Alias{*alias}}, "")
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	if desc.Name != nil {
		_, err = db.SetRoomState(id, userId, &types.NameEventContent{*desc.Name}, "")
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	if desc.Topic != nil {
		_, err = db.SetRoomState(id, userId, &types.TopicEventContent{*desc.Topic}, "")
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	for _, invited := range desc.Invited {
		membership := types.MembershipEventContent{nil, types.MembershipInvited}
		_, err = db.SetRoomState(id, userId, &membership, invited.String())
		if err != nil {
			return types.RoomId{}, nil, err
		}
	}
	return id, alias, nil
}

func (r Room) Id() types.RoomId {
	return r.id
}

func (r Room) AddMessage(user User, content types.TypedContent) (*types.Event, error) {
	return nil, nil
}

func (r Room) SetState(user User, content types.TypedContent, stateKey string) (*types.State, error) {
	userIdStateKey, err := types.ParseUserId(stateKey)
	isUserIdStateKey := err == nil

	eventType := content.EventType()
	switch eventType {
	case types.EventTypeName:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypeTopic:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypeJoinRules:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypePowerLevels:
		if stateKey != "" {
			return nil, types.ForbiddenError("state key must be empty for state " + eventType)
		}
	case types.EventTypeCreate:
		return nil, types.ForbiddenError("cannot set state " + eventType)

	case types.EventTypeAliases:
		return nil, types.ForbiddenError("cannot set state " + eventType)

	case types.EventTypeMembership:
		membership, ok := content.(*types.MembershipEventContent)
		if !ok || membership == nil {
			panic("expected membership event content, got " + reflect.TypeOf(content).String())
		}
		if !isUserIdStateKey {
			return nil, types.ForbiddenError("state key must be a user id for state " + eventType)
		}
		return r.doMembershipChange(user, userIdStateKey, membership)
	}
	if isUserIdStateKey && userIdStateKey != user.Id() {
		return nil, types.ForbiddenError("cannot set the state of another user")
	}
	return nil, nil
}

func (r Room) doMembershipChange(changeBy User, userId types.UserId, membership *types.MembershipEventContent) (*types.State, error) {
	currentMembership, err := r.UserMembership(userId)
	if err != nil {
		return nil, err
	}
	if currentMembership == membership.Membership {
		return nil, types.ForbiddenError("membership change was a no-op")
	}
	membership.UserProfile = nil

	switch membership.Membership {
	case types.MembershipNone:
		if currentMembership != types.MembershipBanned {
			return nil, types.BadJsonError("invalid or missing membership in membership change")
		}
		err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
			return pl.Ban
		})
		if err != nil {
			return nil, err
		}
		if userId == changeBy.Id() {
			return nil, types.ForbiddenError("cannot remove a ban from self")
		}

	case types.MembershipInvited:
		if currentMembership != types.MembershipNone {
			return nil, types.ForbiddenError("could not invite user to room, already have membership '" + currentMembership.String() + "'")
		}
		ok, err := r.AllowsJoinRule(types.JoinRuleInvite)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, types.ForbiddenError("room does not allow join method: " + types.JoinRuleInvite.String())
		}
		err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
			return pl.Invite
		})
		if err != nil {
			return nil, err
		}

	case types.MembershipMember:
		if userId != changeBy.Id() {
			return nil, types.ForbiddenError("cannot force other users to join the room")
		}
		profile, err := changeBy.GetProfile()
		if err != nil {
			return nil, err
		}
		membership.UserProfile = &profile

	case types.MembershipKnocking:
		if userId != changeBy.Id() {
			return nil, types.ForbiddenError("cannot force other users to knock")
		}
		if currentMembership != types.MembershipNone {
			return nil, types.ForbiddenError("could not knock on room, already have membership '" + currentMembership.String() + "'")
		}
		ok, err := r.AllowsJoinRule(types.JoinRuleKnock)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, types.ForbiddenError("room does not allow join method: " + types.JoinRuleKnock.String())
		}

	case types.MembershipLeaving:
		if currentMembership == types.MembershipNone {
			return nil, types.ForbiddenError("tried to leave a room without current membership")
		}
		if currentMembership == types.MembershipBanned {
			return nil, types.ForbiddenError("tried to leave room with current membership '" + types.MembershipBanned.String() + "'")
		}
		if userId != changeBy.Id() {
			err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
				return pl.Kick
			})
			if err != nil {
				return nil, err
			}
		}

	case types.MembershipBanned:
		if userId == changeBy.Id() {
			return nil, types.ForbiddenError("cannot ban self")
		}
		err = r.TestPowerLevel(changeBy.Id(), func(pl *types.PowerLevelsEventContent) int {
			return pl.Ban
		})
		if err != nil {
			return nil, err
		}
	}
	return db.SetRoomState(r.Id(), changeBy.Id(), membership, userId.String())
}

func (r Room) TestPowerLevel(userId types.UserId, powerLevelFunc func(*types.PowerLevelsEventContent) int) error {
	powerLevels, err := r.PowerLevels()
	if err != nil {
		return err
	}
	userPowerLevel, err := r.UserPowerLevel(userId)
	if err != nil {
		return err
	}
	requiredPowerLevel := powerLevelFunc(powerLevels)
	if userPowerLevel < requiredPowerLevel {
		msg := fmt.Sprintf("not enough power level to perform action (%d < %d)", userPowerLevel, requiredPowerLevel)
		return types.ForbiddenError(msg)
	}
	return nil
}

func (r Room) UserMembership(userId types.UserId) (types.Membership, error) {
	state, err := db.GetRoomState(r.Id(), types.EventTypeMembership, userId.String())
	if err != nil {
		return types.MembershipNone, err
	}
	if state == nil {
		return types.MembershipNone, nil
	}
	membership, ok := state.Content.(*types.MembershipEventContent)
	if !ok {
		panic("invalid membership content, was " + reflect.TypeOf(state.Content).String())
	}
	return membership.Membership, nil
}

func (r Room) AllowsJoinRule(joinRule types.JoinRule) (bool, error) {
	state, err := db.GetRoomState(r.Id(), types.EventTypeJoinRules, "")
	if err != nil {
		return false, err
	}
	if state == nil {
		panic("room power levels are invalid or missing: " + r.Id().String())
	}
	joinRules, ok := state.Content.(*types.JoinRulesEventContent)
	if !ok {
		panic("invalid join rule content, was " + reflect.TypeOf(state.Content).String())
	}
	if joinRules.JoinRule != joinRule {
		return false, types.ForbiddenError("room does not allow join rule: " + joinRule.String())
	}
	return true, nil
}

func (r Room) PowerLevels() (*types.PowerLevelsEventContent, error) {
	state, err := db.GetRoomState(r.Id(), types.EventTypePowerLevels, "")
	if err != nil {
		return nil, err
	}
	if state == nil {
		panic("room power levels are invalid or missing: " + r.Id().String())
	}
	powerLevels, ok := state.Content.(*types.PowerLevelsEventContent)
	if !ok {
		panic("invalid power level content, was " + reflect.TypeOf(state.Content).String())
	}
	return powerLevels, nil
}

func (r Room) UserPowerLevel(userId types.UserId) (int, error) {
	powerLevels, err := r.PowerLevels()
	if err != nil {
		return 0, err
	}
	if userLevel, ok := powerLevels.Users[userId]; ok {
		return userLevel, nil
	}
	return powerLevels.UserDefault, nil
}

func (r Room) EventPowerLevel(eventType string) (int, error) {
	powerLevels, err := r.PowerLevels()
	if err != nil {
		return 0, err
	}
	if eventLevel, ok := powerLevels.Events[eventType]; ok {
		return eventLevel, nil
	}
	return powerLevels.EventDefault, nil
}
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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 test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
)

// TestCLI tests the "convert" and "validate" subcommand against a generated .tfplan file.
func TestCLI(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode.")
		return
	}
	// Define the reusable constraints to be use for the test cases.
	type constraint struct {
		name            string
		wantViolation   bool
		wantOutputRegex string
	}
	// Currently, we only test one rule. Moving forward, resource specific rules
	// should be added to increase the coverage.
	alwaysViolate := constraint{name: "always_violate", wantViolation: true, wantOutputRegex: "Constraint GCPAlwaysViolatesConstraintV1.always_violates_all on resource"}

	// Test cases for each type of resource is defined here.
	cases := []struct {
		name        string
		constraints []constraint
	}{
		{name: "bucket"},
		{name: "bucket_iam"},
		{name: "disk"},
		{name: "firewall"},
		{name: "instance"},
		{name: "sql"},
		{name: "example_bigquery_dataset"},
		{name: "example_compute_disk"},
		{name: "example_compute_firewall"},
		{name: "example_compute_instance"},
		{name: "example_container_cluster"},
		{name: "example_organization_iam_binding"},
		{name: "example_organization_iam_member"},
		{name: "example_organization_iam_policy"},
		{name: "example_pubsub_topic"},
		{name: "example_project"},
		{name: "example_project_in_org"},
		{name: "example_project_in_folder"},
		{name: "example_project_iam"},
		{name: "example_project_iam_binding"},
		{name: "example_project_iam_member"},
		{name: "example_project_iam_policy"},
		{name: "example_project_service"},
		{name: "example_sql_database_instance"},
		{name: "example_storage_bucket"},
		{name: "full_compute_firewall"},
		{name: "full_compute_instance"},
		{name: "full_container_cluster"},
		{name: "full_container_node_pool"},
		{name: "full_sql_database_instance"},
		{name: "full_storage_bucket"},
	}
	for i := range cases {
		// Allocate a variable to make sure test can run in parallel.
		c := cases[i]
		// Add default constraints if not set.
		if len(c.constraints) == 0 {
			c.constraints = []constraint{alwaysViolate}
		}

		// Test both offline and online mode.
		for _, offline := range []bool{true, false} {
			offline := offline
			t.Run(fmt.Sprintf("v=0.12/tf=%s/offline=%t", c.name, offline), func(t *testing.T) {
				t.Parallel()
				// Create a temporary directory for running terraform.
				dir, err := ioutil.TempDir(tmpDir, "terraform")
				if err != nil {
					log.Fatal(err)
				}
				defer os.RemoveAll(dir)

				// Generate the <name>.tf and <name>_assets.json files into the temporary directory.
				generateTestFiles(t, "../testdata/templates", dir, c.name+".tf")
				generateTestFiles(t, "../testdata/templates", dir, c.name+".json")

				terraform(t, dir, c.name)

				t.Run("cmd=convert", func(t *testing.T) {
					testConvertCommand(t, dir, c.name, offline)
				})

				for _, ct := range c.constraints {
					t.Run(fmt.Sprintf("cmd=validate/constraint=%s", ct.name), func(t *testing.T) {
						testValidateCommand(t, ct.wantViolation, ct.wantOutputRegex, dir, c.name, offline, ct.name)
					})
				}
			})
		}
	}
}

func testConvertCommand(t *testing.T, dir, name string, offline bool) {
	var payload []byte
	payload = tfvConvert(t, dir, name+".tfplan.json", offline)
	// Verify if the generated assets match the expected.
	var got []google.Asset
	err := json.Unmarshal(payload, &got)
	if err != nil {
		t.Fatalf("unmarshaling: %v", err)
	}
	testfile := filepath.Join(dir, name+".json")
	payload, err = ioutil.ReadFile(testfile)
	if err != nil {
		t.Fatalf("Error reading %v: %v", testfile, err)
	}
	var want []google.Asset
	if err := json.Unmarshal(payload, &want); err != nil {
		t.Fatalf("unmarshaling: %v", err)
	}

	gotJSON := normalizeAssets(t, got, offline)
	wantJSON := normalizeAssets(t, want, offline)
	require.JSONEq(t, string(wantJSON), string(gotJSON))
}

func testValidateCommand(t *testing.T, wantViolation bool, want, dir, name string, offline bool, constraintName string) {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("cannot get current directory: %v", err)
	}
	policyPath := filepath.Join(cwd, samplePolicyPath, constraintName)
	var got []byte
	got = tfvValidate(t, wantViolation, dir, name+".tfplan.json", policyPath, offline)
	wantRe := regexp.MustCompile(want)
	if want != "" && !wantRe.Match(got) {
		t.Fatalf("binary did not return expect output, \ngot=%s \nwant (regex)=%s", string(got), want)
	}
}

func terraform(t *testing.T, dir, name string) {
	terraformInit(t, "terraform", dir)
	terraformPlan(t, "terraform", dir, name+".tfplan")
	payload := terraformShow(t, "terraform", dir, name+".tfplan")
	saveFile(t, dir, name+".tfplan.json", payload)
}

func terraformInit(t *testing.T, executable, dir string) {
	terraformExec(t, executable, dir, "init", "-input=false")
}

func terraformPlan(t *testing.T, executable, dir, tfplan string) {
	terraformExec(t, executable, dir, "plan", "-input=false", "--out", tfplan)
}

func terraformShow(t *testing.T, executable, dir, tfplan string) []byte {
	return terraformExec(t, executable, dir, "show", "--json", tfplan)
}

func terraformExec(t *testing.T, executable, dir string, args ...string) []byte {
	cmd := exec.Command(executable, args...)
	cmd.Env = []string{"HOME=" + filepath.Join(dir, "fakehome")}
	cmd.Dir = dir
	wantError := false
	payload, _ := run(t, cmd, wantError)
	return payload
}

func saveFile(t *testing.T, dir, filename string, payload []byte) {
	fullpath := filepath.Join(dir, filename)
	f, err := os.Create(fullpath)
	if err != nil {
		t.Fatalf("error while creating file %s, error %v", fullpath, err)
	}
	_, err = f.Write(payload)
	if err != nil {
		t.Fatalf("error while writing to file %s, error %v", fullpath, err)
	}
}

func tfvConvert(t *testing.T, dir, tfplan string, offline bool) []byte {
	executable := tfvBinary
	wantError := false
	args := []string{"convert", "--project", data.Provider["project"]}
	if offline {
		args = append(args, "--offline", "--ancestry", data.Ancestry)
	}
	args = append(args, tfplan)
	cmd := exec.Command(executable, args...)
	// Remove environment variables inherited from the test runtime.
	cmd.Env = []string{}
	// Add credentials back.
	if data.Provider["credentials"] != "" {
		cmd.Env = append(cmd.Env, "GOOGLE_APPLICATION_CREDENTIALS="+data.Provider["credentials"])
	}
	cmd.Dir = dir
	payload, _ := run(t, cmd, wantError)
	return payload
}

func tfvValidate(t *testing.T, wantError bool, dir, tfplan, policyPath string, offline bool) []byte {
	executable := tfvBinary
	args := []string{"validate", "--project", data.Provider["project"], "--policy-path", policyPath}
	if offline {
		args = append(args, "--offline", "--ancestry", data.Ancestry)
	}
	args = append(args, tfplan)
	cmd := exec.Command(executable, args...)
	cmd.Env = []string{"GOOGLE_APPLICATION_CREDENTIALS=" + data.Provider["credentials"]}
	cmd.Dir = dir
	payload, _ := run(t, cmd, wantError)
	return payload
}

// run a command and call t.Fatal on non-zero exit.
func run(t *testing.T, cmd *exec.Cmd, wantError bool) ([]byte, []byte) {
	var stderr, stdout bytes.Buffer
	cmd.Stderr, cmd.Stdout = &stderr, &stdout
	err := cmd.Run()
	if gotError := (err != nil); gotError != wantError {
		t.Fatalf("running %s: \nerror=%v \nstderr=%s \nstdout=%s", cmdToString(cmd), err, stderr.String(), stdout.String())
	}
	// Print env, stdout and stderr if verbose flag is used.
	if len(cmd.Env) != 0 {
		t.Logf("=== Environment Variable of %s ===", cmdToString(cmd))
		t.Log(strings.Join(cmd.Env, "\n"))
	}
	if stdout.String() != "" {
		t.Logf("=== STDOUT of %s ===", cmdToString(cmd))
		t.Log(stdout.String())
	}
	if stderr.String() != "" {
		t.Logf("=== STDERR of %s ===", cmdToString(cmd))
		t.Log(stderr.String())
	}
	return stdout.Bytes(), stderr.Bytes()
}

// cmdToString clones the logic of https://golang.org/pkg/os/exec/#Cmd.String.
func cmdToString(c *exec.Cmd) string {
	// report the exact executable path (plus args)
	b := new(strings.Builder)
	b.WriteString(c.Path)
	for _, a := range c.Args[1:] {
		b.WriteByte(' ')
		b.WriteString(a)
	}
	return b.String()
}
<commit_msg>Skipped example_compute_instance in offline tests<commit_after>// Copyright 2019 Google LLC
//
// 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 test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
)

// TestCLI tests the "convert" and "validate" subcommand against a generated .tfplan file.
func TestCLI(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode.")
		return
	}
	// Define the reusable constraints to be use for the test cases.
	type constraint struct {
		name            string
		wantViolation   bool
		wantOutputRegex string
	}
	// Currently, we only test one rule. Moving forward, resource specific rules
	// should be added to increase the coverage.
	alwaysViolate := constraint{name: "always_violate", wantViolation: true, wantOutputRegex: "Constraint GCPAlwaysViolatesConstraintV1.always_violates_all on resource"}

	// Test cases for each type of resource is defined here.
	cases := []struct {
		name        string
		constraints []constraint
	}{
		{name: "bucket"},
		{name: "bucket_iam"},
		{name: "disk"},
		{name: "firewall"},
		{name: "instance"},
		{name: "sql"},
		{name: "example_bigquery_dataset"},
		{name: "example_compute_disk"},
		{name: "example_compute_firewall"},
		{name: "example_compute_instance"},
		{name: "example_container_cluster"},
		{name: "example_organization_iam_binding"},
		{name: "example_organization_iam_member"},
		{name: "example_organization_iam_policy"},
		{name: "example_pubsub_topic"},
		{name: "example_project"},
		{name: "example_project_in_org"},
		{name: "example_project_in_folder"},
		{name: "example_project_iam"},
		{name: "example_project_iam_binding"},
		{name: "example_project_iam_member"},
		{name: "example_project_iam_policy"},
		{name: "example_project_service"},
		{name: "example_sql_database_instance"},
		{name: "example_storage_bucket"},
		{name: "full_compute_firewall"},
		{name: "full_compute_instance"},
		{name: "full_container_cluster"},
		{name: "full_container_node_pool"},
		{name: "full_sql_database_instance"},
		{name: "full_storage_bucket"},
	}

	// Map of cases to skip to reasons for the skip
	skipCases := map[string]string{
		"TestCLI/v=0.12/tf=example_compute_instance/offline=true/cmd=convert":                            "compute_instance doesn't work in offline mode - github.com/hashicorp/terraform-provider-google/issues/8489",
		"TestCLI/v=0.12/tf=example_compute_instance/offline=true/cmd=validate/constraint=always_violate": "compute_instance doesn't work in offline mode - github.com/hashicorp/terraform-provider-google/issues/8489",
	}
	for i := range cases {
		// Allocate a variable to make sure test can run in parallel.
		c := cases[i]
		// Add default constraints if not set.
		if len(c.constraints) == 0 {
			c.constraints = []constraint{alwaysViolate}
		}

		// Test both offline and online mode.
		for _, offline := range []bool{true, false} {
			offline := offline
			t.Run(fmt.Sprintf("v=0.12/tf=%s/offline=%t", c.name, offline), func(t *testing.T) {
				t.Parallel()
				// Create a temporary directory for running terraform.
				dir, err := ioutil.TempDir(tmpDir, "terraform")
				if err != nil {
					log.Fatal(err)
				}
				defer os.RemoveAll(dir)

				// Generate the <name>.tf and <name>_assets.json files into the temporary directory.
				generateTestFiles(t, "../testdata/templates", dir, c.name+".tf")
				generateTestFiles(t, "../testdata/templates", dir, c.name+".json")

				terraform(t, dir, c.name)

				t.Run("cmd=convert", func(t *testing.T) {
					if reason, exists := skipCases[t.Name()]; exists {
						t.Skip(reason)
					}
					testConvertCommand(t, dir, c.name, offline)
				})

				for _, ct := range c.constraints {
					t.Run(fmt.Sprintf("cmd=validate/constraint=%s", ct.name), func(t *testing.T) {
						if reason, exists := skipCases[t.Name()]; exists {
							t.Skip(reason)
						}
						testValidateCommand(t, ct.wantViolation, ct.wantOutputRegex, dir, c.name, offline, ct.name)
					})
				}
			})
		}
	}
}

func testConvertCommand(t *testing.T, dir, name string, offline bool) {
	var payload []byte
	payload = tfvConvert(t, dir, name+".tfplan.json", offline)
	// Verify if the generated assets match the expected.
	var got []google.Asset
	err := json.Unmarshal(payload, &got)
	if err != nil {
		t.Fatalf("unmarshaling: %v", err)
	}
	testfile := filepath.Join(dir, name+".json")
	payload, err = ioutil.ReadFile(testfile)
	if err != nil {
		t.Fatalf("Error reading %v: %v", testfile, err)
	}
	var want []google.Asset
	if err := json.Unmarshal(payload, &want); err != nil {
		t.Fatalf("unmarshaling: %v", err)
	}

	gotJSON := normalizeAssets(t, got, offline)
	wantJSON := normalizeAssets(t, want, offline)
	require.JSONEq(t, string(wantJSON), string(gotJSON))
}

func testValidateCommand(t *testing.T, wantViolation bool, want, dir, name string, offline bool, constraintName string) {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("cannot get current directory: %v", err)
	}
	policyPath := filepath.Join(cwd, samplePolicyPath, constraintName)
	var got []byte
	got = tfvValidate(t, wantViolation, dir, name+".tfplan.json", policyPath, offline)
	wantRe := regexp.MustCompile(want)
	if want != "" && !wantRe.Match(got) {
		t.Fatalf("binary did not return expect output, \ngot=%s \nwant (regex)=%s", string(got), want)
	}
}

func terraform(t *testing.T, dir, name string) {
	terraformInit(t, "terraform", dir)
	terraformPlan(t, "terraform", dir, name+".tfplan")
	payload := terraformShow(t, "terraform", dir, name+".tfplan")
	saveFile(t, dir, name+".tfplan.json", payload)
}

func terraformInit(t *testing.T, executable, dir string) {
	terraformExec(t, executable, dir, "init", "-input=false")
}

func terraformPlan(t *testing.T, executable, dir, tfplan string) {
	terraformExec(t, executable, dir, "plan", "-input=false", "--out", tfplan)
}

func terraformShow(t *testing.T, executable, dir, tfplan string) []byte {
	return terraformExec(t, executable, dir, "show", "--json", tfplan)
}

func terraformExec(t *testing.T, executable, dir string, args ...string) []byte {
	cmd := exec.Command(executable, args...)
	cmd.Env = []string{"HOME=" + filepath.Join(dir, "fakehome")}
	cmd.Dir = dir
	wantError := false
	payload, _ := run(t, cmd, wantError)
	return payload
}

func saveFile(t *testing.T, dir, filename string, payload []byte) {
	fullpath := filepath.Join(dir, filename)
	f, err := os.Create(fullpath)
	if err != nil {
		t.Fatalf("error while creating file %s, error %v", fullpath, err)
	}
	_, err = f.Write(payload)
	if err != nil {
		t.Fatalf("error while writing to file %s, error %v", fullpath, err)
	}
}

func tfvConvert(t *testing.T, dir, tfplan string, offline bool) []byte {
	executable := tfvBinary
	wantError := false
	args := []string{"convert", "--project", data.Provider["project"]}
	if offline {
		args = append(args, "--offline", "--ancestry", data.Ancestry)
	}
	args = append(args, tfplan)
	cmd := exec.Command(executable, args...)
	// Remove environment variables inherited from the test runtime.
	cmd.Env = []string{}
	// Add credentials back.
	if data.Provider["credentials"] != "" {
		cmd.Env = append(cmd.Env, "GOOGLE_APPLICATION_CREDENTIALS="+data.Provider["credentials"])
	}
	cmd.Dir = dir
	payload, _ := run(t, cmd, wantError)
	return payload
}

func tfvValidate(t *testing.T, wantError bool, dir, tfplan, policyPath string, offline bool) []byte {
	executable := tfvBinary
	args := []string{"validate", "--project", data.Provider["project"], "--policy-path", policyPath}
	if offline {
		args = append(args, "--offline", "--ancestry", data.Ancestry)
	}
	args = append(args, tfplan)
	cmd := exec.Command(executable, args...)
	cmd.Env = []string{"GOOGLE_APPLICATION_CREDENTIALS=" + data.Provider["credentials"]}
	cmd.Dir = dir
	payload, _ := run(t, cmd, wantError)
	return payload
}

// run a command and call t.Fatal on non-zero exit.
func run(t *testing.T, cmd *exec.Cmd, wantError bool) ([]byte, []byte) {
	var stderr, stdout bytes.Buffer
	cmd.Stderr, cmd.Stdout = &stderr, &stdout
	err := cmd.Run()
	if gotError := (err != nil); gotError != wantError {
		t.Fatalf("running %s: \nerror=%v \nstderr=%s \nstdout=%s", cmdToString(cmd), err, stderr.String(), stdout.String())
	}
	// Print env, stdout and stderr if verbose flag is used.
	if len(cmd.Env) != 0 {
		t.Logf("=== Environment Variable of %s ===", cmdToString(cmd))
		t.Log(strings.Join(cmd.Env, "\n"))
	}
	if stdout.String() != "" {
		t.Logf("=== STDOUT of %s ===", cmdToString(cmd))
		t.Log(stdout.String())
	}
	if stderr.String() != "" {
		t.Logf("=== STDERR of %s ===", cmdToString(cmd))
		t.Log(stderr.String())
	}
	return stdout.Bytes(), stderr.Bytes()
}

// cmdToString clones the logic of https://golang.org/pkg/os/exec/#Cmd.String.
func cmdToString(c *exec.Cmd) string {
	// report the exact executable path (plus args)
	b := new(strings.Builder)
	b.WriteString(c.Path)
	for _, a := range c.Args[1:] {
		b.WriteByte(' ')
		b.WriteString(a)
	}
	return b.String()
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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 e2e

import (
	"fmt"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"k8s.io/kubernetes/pkg/api"
	client "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/labels"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const (
	smallRCSize       = 5
	mediumRCSize      = 30
	bigRCSize         = 250
	smallRCGroupName  = "load-test-small-rc"
	mediumRCGroupName = "load-test-medium-rc"
	bigRCGroupName    = "load-test-big-rc"
	smallRCBatchSize  = 30
	mediumRCBatchSize = 5
	bigRCBatchSize    = 1
)

// This test suite can take a long time to run, so by default it is added to
// the ginkgo.skip list (see driver.go).
// To run this suite you must explicitly ask for it by setting the
// -t/--test flag or ginkgo.focus flag.
var _ = Describe("Load capacity", func() {
	var c *client.Client
	var nodeCount int
	var ns string
	var configs []*RCConfig

	// Gathers metrics before teardown
	// TODO add flag that allows to skip cleanup on failure
	AfterEach(func() {
		// Verify latency metrics
		highLatencyRequests, err := HighLatencyRequests(c)
		expectNoError(err, "Too many instances metrics above the threshold")
		Expect(highLatencyRequests).NotTo(BeNumerically(">", 0))
	})

	// Explicitly put here, to delete namespace at the end of the test
	// (after measuring latency metrics, etc.).
	options := FrameworkOptions{
		clientQPS:   50,
		clientBurst: 100,
	}
	framework := NewFramework("load", options)
	framework.NamespaceDeletionTimeout = time.Hour

	BeforeEach(func() {
		c = framework.Client

		ns = framework.Namespace.Name
		nodes := ListSchedulableNodesOrDie(c)
		nodeCount = len(nodes.Items)
		Expect(nodeCount).NotTo(BeZero())

		// Terminating a namespace (deleting the remaining objects from it - which
		// generally means events) can affect the current run. Thus we wait for all
		// terminating namespace to be finally deleted before starting this test.
		err := checkTestingNSDeletedExcept(c, ns)
		expectNoError(err)

		expectNoError(resetMetrics(c))
	})

	type Load struct {
		podsPerNode int
		image       string
		command     []string
	}

	loadTests := []Load{
		// The container will consume 1 cpu and 512mb of memory.
		{podsPerNode: 3, image: "jess/stress", command: []string{"stress", "-c", "1", "-m", "2"}},
		{podsPerNode: 30, image: "gcr.io/google_containers/serve_hostname:1.1"},
	}

	for _, testArg := range loadTests {
		name := fmt.Sprintf("should be able to handle %v pods per node", testArg.podsPerNode)
		if testArg.podsPerNode == 30 {
			name = "[Feature:Performance] " + name
		} else {
			name = "[Feature:ManualPerformance] " + name
		}
		itArg := testArg

		It(name, func() {
			totalPods := itArg.podsPerNode * nodeCount
			configs = generateRCConfigs(totalPods, itArg.image, itArg.command, c, ns)

			// Simulate lifetime of RC:
			//  * create with initial size
			//  * scale RC to a random size and list all pods
			//  * scale RC to a random size and list all pods
			//  * delete it
			//
			// This will generate ~5 creations/deletions per second assuming:
			//  - X small RCs each 5 pods   [ 5 * X = totalPods / 2 ]
			//  - Y medium RCs each 30 pods [ 30 * Y = totalPods / 4 ]
			//  - Z big RCs each 250 pods   [ 250 * Z = totalPods / 4]

			// We would like to spread creating replication controllers over time
			// to make it possible to create/schedule them in the meantime.
			// Currently we assume 10 pods/second average throughput.
			creatingTime := time.Duration(totalPods/10) * time.Second
			// TODO: Remove it after speeding up scheduler #22262.
			if nodeCount > 500 {
				creatingTime = time.Duration(totalPods/5) * time.Second
			}
			createAllRC(configs, creatingTime)
			By("============================================================================")

			// We would like to spread scaling replication controllers over time
			// to make it possible to create/schedule & delete them in the meantime.
			// Currently we assume that 10 pods/second average throughput.
			// The expected number of created/deleted pods is less than totalPods/3.
			scalingTime := time.Duration(totalPods/30) * time.Second
			// TODO: Remove it after speeding up scheduler #22262.
			if nodeCount > 500 {
				scalingTime = time.Duration(totalPods/15) * time.Second
			}
			scaleAllRC(configs, scalingTime)
			By("============================================================================")

			scaleAllRC(configs, scalingTime)
			By("============================================================================")

			// Cleanup all created replication controllers.
			// Currently we assume 5 pods/second average deletion throughput.
			deletingTime := time.Duration(totalPods/10) * time.Second
			// TODO: Remove it after speeding up scheduler #22262.
			if nodeCount > 500 {
				deletingTime = time.Duration(totalPods/5) * time.Second
			}
			deleteAllRC(configs, deletingTime)
		})
	}
})

func computeRCCounts(total int) (int, int, int) {
	// Small RCs owns ~0.5 of total number of pods, medium and big RCs ~0.25 each.
	// For example for 3000 pods (100 nodes, 30 pods per node) there are:
	//  - 300 small RCs each 5 pods
	//  - 25 medium RCs each 30 pods
	//  - 3 big RCs each 250 pods
	bigRCCount := total / 4 / bigRCSize
	total -= bigRCCount * bigRCSize
	mediumRCCount := total / 3 / mediumRCSize
	total -= mediumRCCount * mediumRCSize
	smallRCCount := total / smallRCSize
	return smallRCCount, mediumRCCount, bigRCCount
}

func generateRCConfigs(totalPods int, image string, command []string, c *client.Client, ns string) []*RCConfig {
	configs := make([]*RCConfig, 0)

	smallRCCount, mediumRCCount, bigRCCount := computeRCCounts(totalPods)
	configs = append(configs, generateRCConfigsForGroup(c, ns, smallRCGroupName, smallRCSize, smallRCCount, image, command)...)
	configs = append(configs, generateRCConfigsForGroup(c, ns, mediumRCGroupName, mediumRCSize, mediumRCCount, image, command)...)
	configs = append(configs, generateRCConfigsForGroup(c, ns, bigRCGroupName, bigRCSize, bigRCCount, image, command)...)

	return configs
}

func generateRCConfigsForGroup(c *client.Client, ns, groupName string, size, count int, image string, command []string) []*RCConfig {
	configs := make([]*RCConfig, 0, count)
	for i := 1; i <= count; i++ {
		config := &RCConfig{
			Client:     c,
			Name:       groupName + "-" + strconv.Itoa(i),
			Namespace:  ns,
			Timeout:    10 * time.Minute,
			Image:      image,
			Command:    command,
			Replicas:   size,
			CpuRequest: 20,       // 0.02 core
			MemRequest: 52428800, // 50MB
		}
		configs = append(configs, config)
	}
	return configs
}

func sleepUpTo(d time.Duration) {
	time.Sleep(time.Duration(rand.Int63n(d.Nanoseconds())))
}

func createAllRC(configs []*RCConfig, creatingTime time.Duration) {
	var wg sync.WaitGroup
	wg.Add(len(configs))
	for _, config := range configs {
		go createRC(&wg, config, creatingTime)
	}
	wg.Wait()
}

func createRC(wg *sync.WaitGroup, config *RCConfig, creatingTime time.Duration) {
	defer GinkgoRecover()
	defer wg.Done()

	sleepUpTo(creatingTime)
	expectNoError(RunRC(*config), fmt.Sprintf("creating rc %s", config.Name))
}

func scaleAllRC(configs []*RCConfig, scalingTime time.Duration) {
	var wg sync.WaitGroup
	wg.Add(len(configs))
	for _, config := range configs {
		go scaleRC(&wg, config, scalingTime)
	}
	wg.Wait()
}

// Scales RC to a random size within [0.5*size, 1.5*size] and lists all the pods afterwards.
// Scaling happens always based on original size, not the current size.
func scaleRC(wg *sync.WaitGroup, config *RCConfig, scalingTime time.Duration) {
	defer GinkgoRecover()
	defer wg.Done()

	sleepUpTo(scalingTime)
	newSize := uint(rand.Intn(config.Replicas) + config.Replicas/2)
	expectNoError(ScaleRC(config.Client, config.Namespace, config.Name, newSize, true),
		fmt.Sprintf("scaling rc %s for the first time", config.Name))
	selector := labels.SelectorFromSet(labels.Set(map[string]string{"name": config.Name}))
	options := api.ListOptions{
		LabelSelector:   selector,
		ResourceVersion: "0",
	}
	_, err := config.Client.Pods(config.Namespace).List(options)
	expectNoError(err, fmt.Sprintf("listing pods from rc %v", config.Name))
}

func deleteAllRC(configs []*RCConfig, deletingTime time.Duration) {
	var wg sync.WaitGroup
	wg.Add(len(configs))
	for _, config := range configs {
		go deleteRC(&wg, config, deletingTime)
	}
	wg.Wait()
}

func deleteRC(wg *sync.WaitGroup, config *RCConfig, deletingTime time.Duration) {
	defer GinkgoRecover()
	defer wg.Done()

	sleepUpTo(deletingTime)
	expectNoError(DeleteRC(config.Client, config.Namespace, config.Name), fmt.Sprintf("deleting rc %s", config.Name))
}
<commit_msg>Revert "Speed up load test in smaller clusters"<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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 e2e

import (
	"fmt"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"k8s.io/kubernetes/pkg/api"
	client "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/labels"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const (
	smallRCSize       = 5
	mediumRCSize      = 30
	bigRCSize         = 250
	smallRCGroupName  = "load-test-small-rc"
	mediumRCGroupName = "load-test-medium-rc"
	bigRCGroupName    = "load-test-big-rc"
	smallRCBatchSize  = 30
	mediumRCBatchSize = 5
	bigRCBatchSize    = 1
)

// This test suite can take a long time to run, so by default it is added to
// the ginkgo.skip list (see driver.go).
// To run this suite you must explicitly ask for it by setting the
// -t/--test flag or ginkgo.focus flag.
var _ = Describe("Load capacity", func() {
	var c *client.Client
	var nodeCount int
	var ns string
	var configs []*RCConfig

	// Gathers metrics before teardown
	// TODO add flag that allows to skip cleanup on failure
	AfterEach(func() {
		// Verify latency metrics
		highLatencyRequests, err := HighLatencyRequests(c)
		expectNoError(err, "Too many instances metrics above the threshold")
		Expect(highLatencyRequests).NotTo(BeNumerically(">", 0))
	})

	// Explicitly put here, to delete namespace at the end of the test
	// (after measuring latency metrics, etc.).
	options := FrameworkOptions{
		clientQPS:   50,
		clientBurst: 100,
	}
	framework := NewFramework("load", options)
	framework.NamespaceDeletionTimeout = time.Hour

	BeforeEach(func() {
		c = framework.Client

		ns = framework.Namespace.Name
		nodes := ListSchedulableNodesOrDie(c)
		nodeCount = len(nodes.Items)
		Expect(nodeCount).NotTo(BeZero())

		// Terminating a namespace (deleting the remaining objects from it - which
		// generally means events) can affect the current run. Thus we wait for all
		// terminating namespace to be finally deleted before starting this test.
		err := checkTestingNSDeletedExcept(c, ns)
		expectNoError(err)

		expectNoError(resetMetrics(c))
	})

	type Load struct {
		podsPerNode int
		image       string
		command     []string
	}

	loadTests := []Load{
		// The container will consume 1 cpu and 512mb of memory.
		{podsPerNode: 3, image: "jess/stress", command: []string{"stress", "-c", "1", "-m", "2"}},
		{podsPerNode: 30, image: "gcr.io/google_containers/serve_hostname:1.1"},
	}

	for _, testArg := range loadTests {
		name := fmt.Sprintf("should be able to handle %v pods per node", testArg.podsPerNode)
		if testArg.podsPerNode == 30 {
			name = "[Feature:Performance] " + name
		} else {
			name = "[Feature:ManualPerformance] " + name
		}
		itArg := testArg

		It(name, func() {
			totalPods := itArg.podsPerNode * nodeCount
			configs = generateRCConfigs(totalPods, itArg.image, itArg.command, c, ns)

			// Simulate lifetime of RC:
			//  * create with initial size
			//  * scale RC to a random size and list all pods
			//  * scale RC to a random size and list all pods
			//  * delete it
			//
			// This will generate ~5 creations/deletions per second assuming:
			//  - X small RCs each 5 pods   [ 5 * X = totalPods / 2 ]
			//  - Y medium RCs each 30 pods [ 30 * Y = totalPods / 4 ]
			//  - Z big RCs each 250 pods   [ 250 * Z = totalPods / 4]

			// We would like to spread creating replication controllers over time
			// to make it possible to create/schedule them in the meantime.
			// Currently we assume 5 pods/second average throughput.
			// We may want to revisit it in the future.
			creatingTime := time.Duration(totalPods/5) * time.Second
			createAllRC(configs, creatingTime)
			By("============================================================================")

			// We would like to spread scaling replication controllers over time
			// to make it possible to create/schedule & delete them in the meantime.
			// Currently we assume that 5 pods/second average throughput.
			// The expected number of created/deleted pods is less than totalPods/3.
			scalingTime := time.Duration(totalPods/15) * time.Second
			scaleAllRC(configs, scalingTime)
			By("============================================================================")

			scaleAllRC(configs, scalingTime)
			By("============================================================================")

			// Cleanup all created replication controllers.
			// Currently we assume 5 pods/second average deletion throughput.
			// We may want to revisit it in the future.
			deletingTime := time.Duration(totalPods/5) * time.Second
			deleteAllRC(configs, deletingTime)
		})
	}
})

func computeRCCounts(total int) (int, int, int) {
	// Small RCs owns ~0.5 of total number of pods, medium and big RCs ~0.25 each.
	// For example for 3000 pods (100 nodes, 30 pods per node) there are:
	//  - 300 small RCs each 5 pods
	//  - 25 medium RCs each 30 pods
	//  - 3 big RCs each 250 pods
	bigRCCount := total / 4 / bigRCSize
	total -= bigRCCount * bigRCSize
	mediumRCCount := total / 3 / mediumRCSize
	total -= mediumRCCount * mediumRCSize
	smallRCCount := total / smallRCSize
	return smallRCCount, mediumRCCount, bigRCCount
}

func generateRCConfigs(totalPods int, image string, command []string, c *client.Client, ns string) []*RCConfig {
	configs := make([]*RCConfig, 0)

	smallRCCount, mediumRCCount, bigRCCount := computeRCCounts(totalPods)
	configs = append(configs, generateRCConfigsForGroup(c, ns, smallRCGroupName, smallRCSize, smallRCCount, image, command)...)
	configs = append(configs, generateRCConfigsForGroup(c, ns, mediumRCGroupName, mediumRCSize, mediumRCCount, image, command)...)
	configs = append(configs, generateRCConfigsForGroup(c, ns, bigRCGroupName, bigRCSize, bigRCCount, image, command)...)

	return configs
}

func generateRCConfigsForGroup(c *client.Client, ns, groupName string, size, count int, image string, command []string) []*RCConfig {
	configs := make([]*RCConfig, 0, count)
	for i := 1; i <= count; i++ {
		config := &RCConfig{
			Client:     c,
			Name:       groupName + "-" + strconv.Itoa(i),
			Namespace:  ns,
			Timeout:    10 * time.Minute,
			Image:      image,
			Command:    command,
			Replicas:   size,
			CpuRequest: 20,       // 0.02 core
			MemRequest: 52428800, // 50MB
		}
		configs = append(configs, config)
	}
	return configs
}

func sleepUpTo(d time.Duration) {
	time.Sleep(time.Duration(rand.Int63n(d.Nanoseconds())))
}

func createAllRC(configs []*RCConfig, creatingTime time.Duration) {
	var wg sync.WaitGroup
	wg.Add(len(configs))
	for _, config := range configs {
		go createRC(&wg, config, creatingTime)
	}
	wg.Wait()
}

func createRC(wg *sync.WaitGroup, config *RCConfig, creatingTime time.Duration) {
	defer GinkgoRecover()
	defer wg.Done()

	sleepUpTo(creatingTime)
	expectNoError(RunRC(*config), fmt.Sprintf("creating rc %s", config.Name))
}

func scaleAllRC(configs []*RCConfig, scalingTime time.Duration) {
	var wg sync.WaitGroup
	wg.Add(len(configs))
	for _, config := range configs {
		go scaleRC(&wg, config, scalingTime)
	}
	wg.Wait()
}

// Scales RC to a random size within [0.5*size, 1.5*size] and lists all the pods afterwards.
// Scaling happens always based on original size, not the current size.
func scaleRC(wg *sync.WaitGroup, config *RCConfig, scalingTime time.Duration) {
	defer GinkgoRecover()
	defer wg.Done()

	sleepUpTo(scalingTime)
	newSize := uint(rand.Intn(config.Replicas) + config.Replicas/2)
	expectNoError(ScaleRC(config.Client, config.Namespace, config.Name, newSize, true),
		fmt.Sprintf("scaling rc %s for the first time", config.Name))
	selector := labels.SelectorFromSet(labels.Set(map[string]string{"name": config.Name}))
	options := api.ListOptions{
		LabelSelector:   selector,
		ResourceVersion: "0",
	}
	_, err := config.Client.Pods(config.Namespace).List(options)
	expectNoError(err, fmt.Sprintf("listing pods from rc %v", config.Name))
}

func deleteAllRC(configs []*RCConfig, deletingTime time.Duration) {
	var wg sync.WaitGroup
	wg.Add(len(configs))
	for _, config := range configs {
		go deleteRC(&wg, config, deletingTime)
	}
	wg.Wait()
}

func deleteRC(wg *sync.WaitGroup, config *RCConfig, deletingTime time.Duration) {
	defer GinkgoRecover()
	defer wg.Done()

	sleepUpTo(deletingTime)
	expectNoError(DeleteRC(config.Client, config.Namespace, config.Name), fmt.Sprintf("deleting rc %s", config.Name))
}
<|endoftext|>


<commit_before>package testing

import (
	"fmt"
	th "github.com/gophercloud/gophercloud/testhelper"
	fake "github.com/gophercloud/gophercloud/testhelper/client"
	"net/http"
	"testing"
)

const (
	shareEndpoint = "/shares"
	shareID       = "011d21e2-fbc3-4e4a-9993-9ea223f73264"
)

var createRequest = `{
		"share": {
			"name": "my_test_share",
			"size": 1,
			"share_proto": "NFS"
		}
	}`

var createResponse = `{
		"share": {
			"name": "my_test_share",
			"share_proto": "NFS",
			"size": 1,
			"status": null,
			"share_server_id": null,
			"project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
			"share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7",
			"share_type_name": "default",
			"availability_zone": null,
			"created_at": "2015-09-18T10:25:24.533287",
			"export_location": null,
			"links": [
				{
					"href": "http://172.18.198.54:8786/v1/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
					"rel": "self"
				},
				{
					"href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
					"rel": "bookmark"
				}
			],
			"share_network_id": null,
			"export_locations": [],
			"host": null,
			"access_rules_status": "active",
			"has_replicas": false,
			"replication_type": null,
			"task_state": null,
			"snapshot_support": true,
			"consistency_group_id": "9397c191-8427-4661-a2e8-b23820dc01d4",
			"source_cgsnapshot_member_id": null,
			"volume_type": "default",
			"snapshot_id": null,
			"is_public": true,
			"metadata": {
				"project": "my_app",
				"aim": "doc"
			},
			"id": "011d21e2-fbc3-4e4a-9993-9ea223f73264",
			"description": "My custom share London"
		}
	}`

// MockCreateResponse creates a mock response
func MockCreateResponse(t *testing.T) {
	th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "POST")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		th.TestHeader(t, r, "Content-Type", "application/json")
		th.TestHeader(t, r, "Accept", "application/json")
		th.TestJSONRequest(t, r, createRequest)
		w.Header().Add("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, createResponse)
	})
}

// MockDeleteResponse creates a mock delete response
func MockDeleteResponse(t *testing.T) {
	th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "DELETE")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		w.WriteHeader(http.StatusAccepted)
	})
}

var getResponse = `{
    "share": {
        "links": [
            {
                "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
                "rel": "self"
            },
            {
                "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
                "rel": "bookmark"
            }
        ],
        "availability_zone": "nova",
        "share_network_id": "713df749-aac0-4a54-af52-10f6c991e80c",
        "share_server_id": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef",
        "snapshot_id": null,
        "id": "011d21e2-fbc3-4e4a-9993-9ea223f73264",
        "size": 1,
        "share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7",
        "share_type_name": "default",
        "consistency_group_id": "9397c191-8427-4661-a2e8-b23820dc01d4",
        "project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
        "metadata": {
            "project": "my_app",
            "aim": "doc"
        },
        "status": "available",
        "description": "My custom share London",
        "host": "manila2@generic1#GENERIC1",
        "has_replicas": false,
        "replication_type": null,
        "task_state": null,
        "is_public": true,
        "snapshot_support": true,
        "name": "my_test_share",
        "created_at": "2015-09-18T10:25:24.000000",
        "share_proto": "NFS",
        "volume_type": "default",
        "source_cgsnapshot_member_id": null
    }
}`

// MockGetResponse creates a mock get response
func MockGetResponse(t *testing.T) {
	th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, getResponse)
	})
}
<commit_msg>quick formatting fix<commit_after>package testing

import (
	"fmt"
	"net/http"
	"testing"

	th "github.com/gophercloud/gophercloud/testhelper"
	fake "github.com/gophercloud/gophercloud/testhelper/client"
)

const (
	shareEndpoint = "/shares"
	shareID       = "011d21e2-fbc3-4e4a-9993-9ea223f73264"
)

var createRequest = `{
		"share": {
			"name": "my_test_share",
			"size": 1,
			"share_proto": "NFS"
		}
	}`

var createResponse = `{
		"share": {
			"name": "my_test_share",
			"share_proto": "NFS",
			"size": 1,
			"status": null,
			"share_server_id": null,
			"project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
			"share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7",
			"share_type_name": "default",
			"availability_zone": null,
			"created_at": "2015-09-18T10:25:24.533287",
			"export_location": null,
			"links": [
				{
					"href": "http://172.18.198.54:8786/v1/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
					"rel": "self"
				},
				{
					"href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
					"rel": "bookmark"
				}
			],
			"share_network_id": null,
			"export_locations": [],
			"host": null,
			"access_rules_status": "active",
			"has_replicas": false,
			"replication_type": null,
			"task_state": null,
			"snapshot_support": true,
			"consistency_group_id": "9397c191-8427-4661-a2e8-b23820dc01d4",
			"source_cgsnapshot_member_id": null,
			"volume_type": "default",
			"snapshot_id": null,
			"is_public": true,
			"metadata": {
				"project": "my_app",
				"aim": "doc"
			},
			"id": "011d21e2-fbc3-4e4a-9993-9ea223f73264",
			"description": "My custom share London"
		}
	}`

// MockCreateResponse creates a mock response
func MockCreateResponse(t *testing.T) {
	th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "POST")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		th.TestHeader(t, r, "Content-Type", "application/json")
		th.TestHeader(t, r, "Accept", "application/json")
		th.TestJSONRequest(t, r, createRequest)
		w.Header().Add("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, createResponse)
	})
}

// MockDeleteResponse creates a mock delete response
func MockDeleteResponse(t *testing.T) {
	th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "DELETE")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		w.WriteHeader(http.StatusAccepted)
	})
}

var getResponse = `{
    "share": {
        "links": [
            {
                "href": "http://172.18.198.54:8786/v2/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
                "rel": "self"
            },
            {
                "href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
                "rel": "bookmark"
            }
        ],
        "availability_zone": "nova",
        "share_network_id": "713df749-aac0-4a54-af52-10f6c991e80c",
        "share_server_id": "e268f4aa-d571-43dd-9ab3-f49ad06ffaef",
        "snapshot_id": null,
        "id": "011d21e2-fbc3-4e4a-9993-9ea223f73264",
        "size": 1,
        "share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7",
        "share_type_name": "default",
        "consistency_group_id": "9397c191-8427-4661-a2e8-b23820dc01d4",
        "project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
        "metadata": {
            "project": "my_app",
            "aim": "doc"
        },
        "status": "available",
        "description": "My custom share London",
        "host": "manila2@generic1#GENERIC1",
        "has_replicas": false,
        "replication_type": null,
        "task_state": null,
        "is_public": true,
        "snapshot_support": true,
        "name": "my_test_share",
        "created_at": "2015-09-18T10:25:24.000000",
        "share_proto": "NFS",
        "volume_type": "default",
        "source_cgsnapshot_member_id": null
    }
}`

// MockGetResponse creates a mock get response
func MockGetResponse(t *testing.T) {
	th.Mux.HandleFunc(shareEndpoint+"/"+shareID, func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, getResponse)
	})
}
<|endoftext|>


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

// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
// +build cgo

package user

import (
	"fmt"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"unsafe"
)

/*
#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>

static int mygetpwuid_r(int uid, struct passwd *pwd,
	char *buf, size_t buflen, struct passwd **result) {
	return getpwuid_r(uid, pwd, buf, buflen, result);
}

static int mygetpwnam_r(const char *name, struct passwd *pwd,
	char *buf, size_t buflen, struct passwd **result) {
	return getpwnam_r(name, pwd, buf, buflen, result);
}
*/
import "C"

func current() (*User, error) {
	return lookupUnix(syscall.Getuid(), "", false)
}

func lookup(username string) (*User, error) {
	return lookupUnix(-1, username, true)
}

func lookupId(uid string) (*User, error) {
	i, e := strconv.Atoi(uid)
	if e != nil {
		return nil, e
	}
	return lookupUnix(i, "", false)
}

func lookupUnix(uid int, username string, lookupByName bool) (*User, error) {
	var pwd C.struct_passwd
	var result *C.struct_passwd

	var bufSize C.long
	if runtime.GOOS == "dragonfly" || runtime.GOOS == "freebsd" {
		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX
		// and just return -1.  So just use the same
		// size that Linux returns.
		bufSize = 1024
	} else {
		bufSize = C.sysconf(C._SC_GETPW_R_SIZE_MAX)
		if bufSize <= 0 || bufSize > 1<<20 {
			return nil, fmt.Errorf("user: unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize)
		}
	}
	buf := C.malloc(C.size_t(bufSize))
	defer C.free(buf)
	var rv C.int
	if lookupByName {
		nameC := C.CString(username)
		defer C.free(unsafe.Pointer(nameC))
		// mygetpwnam_r is a wrapper around getpwnam_r to avoid
		// passing a size_t to getpwnam_r, because for unknown
		// reasons passing a size_t to getpwnam_r doesn't work on
		// Solaris.
		rv = C.mygetpwnam_r(nameC,
			&pwd,
			(*C.char)(buf),
			C.size_t(bufSize),
			&result)
		if rv != 0 {
			return nil, fmt.Errorf("user: lookup username %s: %s", username, syscall.Errno(rv))
		}
		if result == nil {
			return nil, UnknownUserError(username)
		}
	} else {
		// mygetpwuid_r is a wrapper around getpwuid_r to
		// to avoid using uid_t because C.uid_t(uid) for
		// unknown reasons doesn't work on linux.
		rv = C.mygetpwuid_r(C.int(uid),
			&pwd,
			(*C.char)(buf),
			C.size_t(bufSize),
			&result)
		if rv != 0 {
			return nil, fmt.Errorf("user: lookup userid %d: %s", uid, syscall.Errno(rv))
		}
		if result == nil {
			return nil, UnknownUserIdError(uid)
		}
	}
	u := &User{
		Uid:      strconv.Itoa(int(pwd.pw_uid)),
		Gid:      strconv.Itoa(int(pwd.pw_gid)),
		Username: C.GoString(pwd.pw_name),
		Name:     C.GoString(pwd.pw_gecos),
		HomeDir:  C.GoString(pwd.pw_dir),
	}
	// The pw_gecos field isn't quite standardized.  Some docs
	// say: "It is expected to be a comma separated list of
	// personal data where the first item is the full name of the
	// user."
	if i := strings.Index(u.Name, ","); i >= 0 {
		u.Name = u.Name[:i]
	}
	return u, nil
}
<commit_msg>os/user: don't depend on _SC_GETPW_R_SIZE_MAX on Linux<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
// +build cgo

package user

import (
	"fmt"
	"strconv"
	"strings"
	"syscall"
	"unsafe"
)

/*
#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>

static int mygetpwuid_r(int uid, struct passwd *pwd,
	char *buf, size_t buflen, struct passwd **result) {
	return getpwuid_r(uid, pwd, buf, buflen, result);
}

static int mygetpwnam_r(const char *name, struct passwd *pwd,
	char *buf, size_t buflen, struct passwd **result) {
	return getpwnam_r(name, pwd, buf, buflen, result);
}
*/
import "C"

func current() (*User, error) {
	return lookupUnix(syscall.Getuid(), "", false)
}

func lookup(username string) (*User, error) {
	return lookupUnix(-1, username, true)
}

func lookupId(uid string) (*User, error) {
	i, e := strconv.Atoi(uid)
	if e != nil {
		return nil, e
	}
	return lookupUnix(i, "", false)
}

func lookupUnix(uid int, username string, lookupByName bool) (*User, error) {
	var pwd C.struct_passwd
	var result *C.struct_passwd

	bufSize := C.sysconf(C._SC_GETPW_R_SIZE_MAX)
	if bufSize == -1 {
		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
		// Additionally, not all Linux systems have it, either. For
		// example, the musl libc returns -1.
		bufSize = 1024
	}
	if bufSize <= 0 || bufSize > 1<<20 {
		return nil, fmt.Errorf("user: unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize)
	}
	buf := C.malloc(C.size_t(bufSize))
	defer C.free(buf)
	var rv C.int
	if lookupByName {
		nameC := C.CString(username)
		defer C.free(unsafe.Pointer(nameC))
		// mygetpwnam_r is a wrapper around getpwnam_r to avoid
		// passing a size_t to getpwnam_r, because for unknown
		// reasons passing a size_t to getpwnam_r doesn't work on
		// Solaris.
		rv = C.mygetpwnam_r(nameC,
			&pwd,
			(*C.char)(buf),
			C.size_t(bufSize),
			&result)
		if rv != 0 {
			return nil, fmt.Errorf("user: lookup username %s: %s", username, syscall.Errno(rv))
		}
		if result == nil {
			return nil, UnknownUserError(username)
		}
	} else {
		// mygetpwuid_r is a wrapper around getpwuid_r to
		// to avoid using uid_t because C.uid_t(uid) for
		// unknown reasons doesn't work on linux.
		rv = C.mygetpwuid_r(C.int(uid),
			&pwd,
			(*C.char)(buf),
			C.size_t(bufSize),
			&result)
		if rv != 0 {
			return nil, fmt.Errorf("user: lookup userid %d: %s", uid, syscall.Errno(rv))
		}
		if result == nil {
			return nil, UnknownUserIdError(uid)
		}
	}
	u := &User{
		Uid:      strconv.Itoa(int(pwd.pw_uid)),
		Gid:      strconv.Itoa(int(pwd.pw_gid)),
		Username: C.GoString(pwd.pw_name),
		Name:     C.GoString(pwd.pw_gecos),
		HomeDir:  C.GoString(pwd.pw_dir),
	}
	// The pw_gecos field isn't quite standardized.  Some docs
	// say: "It is expected to be a comma separated list of
	// personal data where the first item is the full name of the
	// user."
	if i := strings.Index(u.Name, ","); i >= 0 {
		u.Name = u.Name[:i]
	}
	return u, nil
}
<|endoftext|>