<commit_before>package main

import (
    "os"
    "fmt"
    "time"
    "math/rand"
    "net/rpc"
    "net"
    "net/http"
//    "errors"
)

//methods we want to export:
//	leader heartbeats - periodically sends to followers for proof of life
//	candidate vote requests - sends to other servers for vote response
//	follower votes

//incoming args
type Args struct {
    msg, vote string
}

//type to export
type someMsg string

//leader heartbeat comes as a string, just send it back to reset timeout
func (t *someMsg) leader_heartbeats(args *Args, reply *string) error {
    *reply = args.msg
    return nil
}

//vote requests come as string, just send it back to increment internal vote count
func (t *someMsg) candidate_vote_requests(args *Args, reply *string) error{
    *reply = args.msg
    return nil
}

//not sure when this is called - reply might be int?
func (t *someMsg) voter_votes (args *Args, reply *string) error{
    *reply = args.vote
    return nil
}

func main() {

    // validate arguments or print usage
    if len(os.Args) < 2 {
        fmt.Println("usage:", os.Args[0], "thisAddress [thatAddress]...")
        os.Exit(1)
    }

    // server calls for HTTP service
    newSomeMsg := new(someMsg)
    rpc.Register(newSomeMsg)
    rpc.HandleHTTP()
    l, e := net.Listen("tcp", ":1234")
    if e != nil {
	fmt.Println("listen error:", e)
	os.Exit(1)
    }
    go http.Serve(l, nil)

    // process id
    pid := os.Getpid()

    // state
    state := "follower"
    fmt.Println(pid, "INITIAL STATE", state)

    // term number
    term := 0

    // address of this server
    thisAddress := os.Args[1]
    fmt.Println(pid, "LISTEN", thisAddress)

    // addresses of other servers
    thatAddress := os.Args[2:]
    for _,address := range thatAddress {
        fmt.Println(pid, "PEER", address)
    }

    // address of leader
//    leadAddress := ""

    // cluster size
    clusterSize := len(os.Args[1:])
    fmt.Println(pid, "CLUSTER SIZE", clusterSize)

    // votes
    votes := 0

    // election timeout between 1500 and 3000ms
    rand.Seed(int64(pid))
    number :=  1500 + rand.Intn(1500)
    electionTimeout := time.Millisecond * time.Duration(number)
    fmt.Println(pid, "RANDOM TIMEOUT", electionTimeout)

    // heartbeat timeout
    heartbeatTimeout := time.Millisecond * time.Duration(1000)

    // vote timeout
    voteTimeout := time.Millisecond * time.Duration(1000)

    /********receive messages from leader on channel*******/
    //invoke client - client dials server. this makes the client channel
    client, err := rpc.DialHTTP("tcp", thisAddress)
    if err != nil {
	fmt.Println("dialing:", err)
	os.Exit(1)
}

    //this should asynchronously receive the message from the leader
    //should this be in event loop?
    leaderMsg := make(chan error, 1)
    leaderMsg = client.Call("someMsg.leader_heartbeats", "msg", "msg")
    select {
	case err := <-leaderMsg:
	    fmt.Println("leader heartbeat response error:", err)
	case <-time.After(heartbeatTimeout):
	    //TODO
	    //become candidate
    }
    fmt.Println("leader heartbeat received")
    /*******************************************************/

    // event loop
    for {

        switch state {

        case "follower":

            select {

            // receive leader message before timeout
            case <-leaderMsg:
                fmt.Println(pid, "LEADER MESSAGE RECEIVED")

            // otherwise begin election
            case <-time.After(electionTimeout):
                state = "candidate"
                fmt.Println(pid, "ELECTION TIMEOUT")
            }

        case "candidate":

            // increment term
            term++
            fmt.Println(pid, "TERM", term)

            // vote for self
            votes = 1

            // request votes
            // TODO

            // receive messages from voters on channel
            // TODO
            voterMsg := make(chan error)
//          voterMsg := client.Go("", args, foo, nil)

            election: for {
                select {

                // receive votes
                case <-voterMsg:
                    fmt.Println(pid, "VOTE RECEIVED")
                    votes++

                    // if majority of votes, go to leader state
                    if votes > clusterSize/2 {
                        state = "leader"
                        break election
                    }

                // receive leader challenge
                case <-leaderMsg:
                    fmt.Println(pid, "LEADER CHALLENGE RECEIVED")

                    // if that term >= this term, return to follower state
                    // TODO
                    if true {
                        state = "follower"
                        break election
                    }

                // time out and start new election
                case <-time.After(voteTimeout):
                    fmt.Println(pid, "VOTE TIMEOUT")
                    break election
                }
            }

        case "leader":

            // send heartbeat
            // TODO

            // wait
            time.Sleep(heartbeatTimeout)

        }
    }
}
<commit_msg>rpc progress<commit_after>package main

import (
    "os"
    "fmt"
    "time"
    "math/rand"
    "net"
    "net/rpc"
)

type Heartbeat struct {
    LeaderID string
    Term int
}

// type HeartbeatResponse struct {
//     Success bool
//     Term int
// }

type VoteRequest struct {
    CandidateID string
    Term int
}

type VoteResponse struct {
    VoteGranted bool
    Term int
}

var leaderMsg chan Heartbeat
//var followerMsg chan HeartbeatResponse
var candidateMsg chan VoteRequest
var voterMsg chan VoteResponse

type Message int

//
func (t *Message) AppendEntries(heartbeat Heartbeat, heartbeatResponse *int) error {
    leaderMsg <- heartbeat
    return nil
}

//
func (s *Message) RequestVote(voteRequest VoteRequest, voteResponse *VoteResponse) error {
    candidateMsg <- voteRequest
    *voteResponse = <-voterMsg
    return nil
}

func main() {

    // validate arguments or print usage
    if len(os.Args) < 2 {
        fmt.Println("usage:", os.Args[0], "thisAddress [thatAddress]...")
        os.Exit(1)
    }

    // process id
    pid := os.Getpid()

    // state
    state := "follower"
    fmt.Println(pid, "INITIAL STATE", state)

    // term number
    term := 0

    // address of this server
    thisAddress := os.Args[1]
    fmt.Println(pid, "LISTEN", thisAddress)

    // addresses of other servers
    thatAddress := os.Args[2:]
    for _,address := range thatAddress {
        fmt.Println(pid, "PEER", address)
    }

    // address of leader
//    leadAddress := ""

    // cluster size
    clusterSize := len(os.Args[1:])
    fmt.Println(pid, "CLUSTER SIZE", clusterSize)

    // votes
    votes := 0

    // election timeout between 1500 and 3000ms
    rand.Seed(int64(pid))
    number :=  1500 + rand.Intn(1500)
    electionTimeout := time.Millisecond * time.Duration(number)
    fmt.Println(pid, "RANDOM TIMEOUT", electionTimeout)

    // heartbeat timeout
    heartbeatTimeout := time.Millisecond * time.Duration(1000)

    // vote timeout
    voteTimeout := time.Millisecond * time.Duration(1000)

    //
    leaderMsg = make(chan Heartbeat)
//    followerMsg = make(chan HeartbeatResponse)
    candidateMsg = make(chan VoteRequest)
    voterMsg = make(chan VoteResponse)

    //
    messages, error := net.Listen("tcp", thisAddress)
    if error != nil {
        fmt.Println(pid, "UNABLE TO LISTEN ON", thisAddress)
        os.Exit(1)
    }
    go rpc.Accept(messages)

    // event loop
    for {

        switch state {

        case "follower":

            select {

            // receive leader message before timeout
            case <-leaderMsg:
                fmt.Println(pid, "LEADER MESSAGE RECEIVED")
//                followerMsg <- HeartbeatResponse{Success: true, Term: term}

            // receive vote request
            case <-candidateMsg:
                fmt.Println(pid, "CANDIDATE MESSAGE RECEIVED")
                voterMsg <- VoteResponse{VoteGranted: true, Term: term}

            // otherwise begin election
            case <-time.After(electionTimeout):
                state = "candidate"
                fmt.Println(pid, "ELECTION TIMEOUT")
            }

        case "candidate":

            // increment term
            term++
            fmt.Println(pid, "TERM", term)

            // vote for self
            votes = 1

            // request votes
            // vreq := VoteRequest{CandidateID: thisAddress, Term: term}
            // vresp := VoteResponse{}
            // for _,address := range thatAddress {
            //     client, error := rpc.Dial("tcp", address)
            //     if error != nil {
            //         fmt.Println(pid, "UNABLE TO DIAL", address)
            //     }
            //     client.Go("Message.AppendEntries", vreq, &vresp, nil)
            // }

            election: for {
                select {

                // receive votes
                case <-voterMsg:
                    fmt.Println(pid, "VOTE RECEIVED")
                    votes++

                    // if majority of votes, go to leader state
                    if votes > clusterSize/2 {
                        state = "leader"
                        break election
                    }

                // receive leader challenge
                case <-leaderMsg:
                    fmt.Println(pid, "LEADER CHALLENGE RECEIVED")

                    // if that term >= this term, return to follower state
                    // TODO
                    if true {
                        state = "follower"
                        break election
                    }

                // time out and start new election
                case <-time.After(voteTimeout):
                    fmt.Println(pid, "VOTE TIMEOUT")
                    break election
                }
            }

        case "leader":

            // send heartbeat
            hb := Heartbeat{LeaderID: thisAddress, Term: term}
            for _,address := range thatAddress {
                client, error := rpc.Dial("tcp", address)
                if error != nil {
                    fmt.Println(pid, "UNABLE TO DIAL", address)
                } else {
                    fmt.Println(pid, "SEND HEARTBEAT TO", address)
                }
                client.Go("Message.AppendEntries", hb, nil, nil)
            }

            // wait
            time.Sleep(heartbeatTimeout)

        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. 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 inode_test

import (
	"os"
	"path"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/fs/inode"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/ogletest"
)

func TestDir(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const inodeID = 17
const inodeName = "foo/bar/"
const typeCacheTTL = time.Second

type DirTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	clock  timeutil.SimulatedClock

	in *inode.DirInode
}

var _ SetUpInterface = &DirTest{}
var _ TearDownInterface = &DirTest{}

func init() { RegisterTestSuite(&DirTest{}) }

func (t *DirTest) SetUp(ti *TestInfo) {
	t.ctx = ti.Ctx
	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")

	// Create the inode. No implicit dirs by default.
	t.resetInode(false)
}

func (t *DirTest) TearDown() {
	t.in.Unlock()
}

func (t *DirTest) resetInode(implicitDirs bool) {
	if t.in != nil {
		t.in.Unlock()
	}

	t.in = inode.NewDirInode(
		inodeID,
		inodeName,
		implicitDirs,
		typeCacheTTL,
		t.bucket,
		&t.clock)

	t.in.Lock()
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *DirTest) ID() {
	ExpectEq(inodeID, t.in.ID())
}

func (t *DirTest) Name() {
	ExpectEq(inodeName, t.in.Name())
}

func (t *DirTest) LookupCount() {
	// Increment thrice. The count should now be three.
	t.in.IncrementLookupCount()
	t.in.IncrementLookupCount()
	t.in.IncrementLookupCount()

	// Decrementing twice shouldn't cause destruction. But one more should.
	AssertFalse(t.in.DecrementLookupCount(2))
	ExpectTrue(t.in.DecrementLookupCount(1))
}

func (t *DirTest) Attributes() {
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0700)|os.ModeDir, attrs.Mode)
}

func (t *DirTest) LookUpChild_NonExistent() {
	o, err := t.in.LookUpChild(t.ctx, "qux")

	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_FileOnly() {
	const name = "qux"
	objName := path.Join(inodeName, name)

	var o *gcs.Object
	var err error

	// Create a backing object.
	createObj, err := gcsutil.CreateObject(t.ctx, t.bucket, objName, "taco")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(objName, o.Name)
	ExpectEq(createObj.Generation, o.Generation)
	ExpectEq(createObj.Size, o.Size)

	// A conflict marker name shouldn't work.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_DirOnly() {
	const name = "qux"
	objName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Create a backing object.
	createObj, err := gcsutil.CreateObject(t.ctx, t.bucket, objName, "")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(objName, o.Name)
	ExpectEq(createObj.Generation, o.Generation)
	ExpectEq(createObj.Size, o.Size)

	// A conflict marker name shouldn't work.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_ImplicitDirOnly_Disabled() {
	const name = "qux"

	var o *gcs.Object
	var err error

	// Create an object that implicitly defines the directory.
	otherObjName := path.Join(inodeName, name) + "/asdf"
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, otherObjName, "")
	AssertEq(nil, err)

	// Looking up the name shouldn't work.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	ExpectEq(nil, o)

	// Ditto with a conflict marker.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_ImplicitDirOnly_Enabled() {
	const name = "qux"
	objName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Enable implicit dirs.
	t.resetInode(true)

	// Create an object that implicitly defines the directory.
	otherObjName := path.Join(objName, "asdf")
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, otherObjName, "")
	AssertEq(nil, err)

	// Looking up the name should work.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(objName, o.Name)
	ExpectEq(0, o.Generation)

	// A conflict marker should not work.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_FileAndDir() {
	const name = "qux"
	fileObjName := path.Join(inodeName, name)
	dirObjName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Create backing objects.
	fileObj, err := gcsutil.CreateObject(t.ctx, t.bucket, fileObjName, "taco")
	AssertEq(nil, err)

	dirObj, err := gcsutil.CreateObject(t.ctx, t.bucket, dirObjName, "")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(dirObjName, o.Name)
	ExpectEq(dirObj.Generation, o.Generation)
	ExpectEq(dirObj.Size, o.Size)

	// Look up with the conflict marker name.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(fileObjName, o.Name)
	ExpectEq(fileObj.Generation, o.Generation)
	ExpectEq(fileObj.Size, o.Size)
}

func (t *DirTest) LookUpChild_FileAndDirAndImplicitDir_Disabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) LookUpChild_FileAndDirAndImplicitDir_Enabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) LookUpChild_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_Empty() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsDisabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsEnabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_LotsOfEntries() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildFile_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildFile_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildFile_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildDir_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildDir_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildDir_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildFile_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildFile_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildFile_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildDir_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildDir_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildDir_TypeCaching() {
	AssertTrue(false, "TODO")
}
<commit_msg>DirTest.LookUpChild_FileAndDirAndImplicitDir_Disabled<commit_after>// Copyright 2015 Google Inc. 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 inode_test

import (
	"os"
	"path"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/fs/inode"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/ogletest"
)

func TestDir(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const inodeID = 17
const inodeName = "foo/bar/"
const typeCacheTTL = time.Second

type DirTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	clock  timeutil.SimulatedClock

	in *inode.DirInode
}

var _ SetUpInterface = &DirTest{}
var _ TearDownInterface = &DirTest{}

func init() { RegisterTestSuite(&DirTest{}) }

func (t *DirTest) SetUp(ti *TestInfo) {
	t.ctx = ti.Ctx
	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")

	// Create the inode. No implicit dirs by default.
	t.resetInode(false)
}

func (t *DirTest) TearDown() {
	t.in.Unlock()
}

func (t *DirTest) resetInode(implicitDirs bool) {
	if t.in != nil {
		t.in.Unlock()
	}

	t.in = inode.NewDirInode(
		inodeID,
		inodeName,
		implicitDirs,
		typeCacheTTL,
		t.bucket,
		&t.clock)

	t.in.Lock()
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *DirTest) ID() {
	ExpectEq(inodeID, t.in.ID())
}

func (t *DirTest) Name() {
	ExpectEq(inodeName, t.in.Name())
}

func (t *DirTest) LookupCount() {
	// Increment thrice. The count should now be three.
	t.in.IncrementLookupCount()
	t.in.IncrementLookupCount()
	t.in.IncrementLookupCount()

	// Decrementing twice shouldn't cause destruction. But one more should.
	AssertFalse(t.in.DecrementLookupCount(2))
	ExpectTrue(t.in.DecrementLookupCount(1))
}

func (t *DirTest) Attributes() {
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0700)|os.ModeDir, attrs.Mode)
}

func (t *DirTest) LookUpChild_NonExistent() {
	o, err := t.in.LookUpChild(t.ctx, "qux")

	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_FileOnly() {
	const name = "qux"
	objName := path.Join(inodeName, name)

	var o *gcs.Object
	var err error

	// Create a backing object.
	createObj, err := gcsutil.CreateObject(t.ctx, t.bucket, objName, "taco")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(objName, o.Name)
	ExpectEq(createObj.Generation, o.Generation)
	ExpectEq(createObj.Size, o.Size)

	// A conflict marker name shouldn't work.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_DirOnly() {
	const name = "qux"
	objName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Create a backing object.
	createObj, err := gcsutil.CreateObject(t.ctx, t.bucket, objName, "")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(objName, o.Name)
	ExpectEq(createObj.Generation, o.Generation)
	ExpectEq(createObj.Size, o.Size)

	// A conflict marker name shouldn't work.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_ImplicitDirOnly_Disabled() {
	const name = "qux"

	var o *gcs.Object
	var err error

	// Create an object that implicitly defines the directory.
	otherObjName := path.Join(inodeName, name) + "/asdf"
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, otherObjName, "")
	AssertEq(nil, err)

	// Looking up the name shouldn't work.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	ExpectEq(nil, o)

	// Ditto with a conflict marker.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_ImplicitDirOnly_Enabled() {
	const name = "qux"
	objName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Enable implicit dirs.
	t.resetInode(true)

	// Create an object that implicitly defines the directory.
	otherObjName := path.Join(objName, "asdf")
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, otherObjName, "")
	AssertEq(nil, err)

	// Looking up the name should work.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(objName, o.Name)
	ExpectEq(0, o.Generation)

	// A conflict marker should not work.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	ExpectEq(nil, o)
}

func (t *DirTest) LookUpChild_FileAndDir() {
	const name = "qux"
	fileObjName := path.Join(inodeName, name)
	dirObjName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Create backing objects.
	fileObj, err := gcsutil.CreateObject(t.ctx, t.bucket, fileObjName, "taco")
	AssertEq(nil, err)

	dirObj, err := gcsutil.CreateObject(t.ctx, t.bucket, dirObjName, "")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(dirObjName, o.Name)
	ExpectEq(dirObj.Generation, o.Generation)
	ExpectEq(dirObj.Size, o.Size)

	// Look up with the conflict marker name.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(fileObjName, o.Name)
	ExpectEq(fileObj.Generation, o.Generation)
	ExpectEq(fileObj.Size, o.Size)
}

func (t *DirTest) LookUpChild_FileAndDirAndImplicitDir_Disabled() {
	const name = "qux"
	fileObjName := path.Join(inodeName, name)
	dirObjName := path.Join(inodeName, name) + "/"

	var o *gcs.Object
	var err error

	// Create backing objects.
	fileObj, err := gcsutil.CreateObject(t.ctx, t.bucket, fileObjName, "taco")
	AssertEq(nil, err)

	dirObj, err := gcsutil.CreateObject(t.ctx, t.bucket, dirObjName, "")
	AssertEq(nil, err)

	// Create an object that implicitly defines the directory.
	otherObjName := path.Join(inodeName, name) + "/asdf"
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, otherObjName, "")
	AssertEq(nil, err)

	// Look up with the proper name.
	o, err = t.in.LookUpChild(t.ctx, name)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(dirObjName, o.Name)
	ExpectEq(dirObj.Generation, o.Generation)
	ExpectEq(dirObj.Size, o.Size)

	// Look up with the conflict marker name.
	o, err = t.in.LookUpChild(t.ctx, name+inode.ConflictingFileNameSuffix)
	AssertEq(nil, err)
	AssertNe(nil, o)

	ExpectEq(fileObjName, o.Name)
	ExpectEq(fileObj.Generation, o.Generation)
	ExpectEq(fileObj.Size, o.Size)
}

func (t *DirTest) LookUpChild_FileAndDirAndImplicitDir_Enabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) LookUpChild_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_Empty() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsDisabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsEnabled() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_LotsOfEntries() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) ReadEntries_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildFile_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildFile_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildFile_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildDir_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildDir_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) CreateChildDir_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildFile_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildFile_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildFile_TypeCaching() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildDir_DoesntExist() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildDir_Exists() {
	AssertTrue(false, "TODO")
}

func (t *DirTest) DeleteChildDir_TypeCaching() {
	AssertTrue(false, "TODO")
}
<|endoftext|>


<commit_before>// run

// Copyright 2019 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.

// Do not panic on conversion to anonymous interface, which
// is similar-looking interface types in different packages.

package main

import (
	ssa1 "./p1/ssa"
	ssa2 "./p2/ssa"
)

func main() {
	v1 := &ssa1.T{}
	_ = v1

	v2 := &ssa2.T{}
	ssa2.Works(v2)
	ssa2.Panics(v2) // This call must not panic
}
<commit_msg>test: add test coverage for type-switch hash collisions<commit_after>// run

// Copyright 2019 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.

// Do not panic on conversion to anonymous interface, which
// is similar-looking interface types in different packages.

package main

import (
	"fmt"

	ssa1 "./p1/ssa"
	ssa2 "./p2/ssa"
)

func main() {
	v1 := &ssa1.T{}
	_ = v1

	v2 := &ssa2.T{}
	ssa2.Works(v2)
	ssa2.Panics(v2) // This call must not panic

	swt(v1, 1)
	swt(v2, 2)
}

//go:noinline
func swt(i interface{}, want int) {
	var got int
	switch i.(type) {
	case *ssa1.T:
		got = 1
	case *ssa2.T:
		got = 2

	case int8, int16, int32, int64:
		got = 3
	case uint8, uint16, uint32, uint64:
		got = 4
	}

	if got != want {
		panic(fmt.Sprintf("switch %v: got %d, want %d", i, got, want))
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// 单例对象管理.
// 框架内置了一些核心对象获取方法，并且可以通过Set和Get方法实现IoC以及对内置核心对象的自定义替换
package gins

import (
    "strconv"
    "gitee.com/johng/gf/g/os/gcfg"
    "gitee.com/johng/gf/g/os/gcmd"
    "gitee.com/johng/gf/g/os/genv"
    "gitee.com/johng/gf/g/os/gview"
    "gitee.com/johng/gf/g/os/gfile"
    "gitee.com/johng/gf/g/database/gdb"
    "gitee.com/johng/gf/g/container/gmap"
)

const (
    gFRAME_CORE_COMPONENT_NAME_VIEW       = "gf.core.component.view"
    gFRAME_CORE_COMPONENT_NAME_CONFIG     = "gf.core.component.config"
    gFRAME_CORE_COMPONENT_NAME_DATABASE   = "gf.core.component.database"
)

// 单例对象存储器
var instances = gmap.NewStringInterfaceMap()

// 获取单例对象
func Get(k string) interface{} {
    return instances.Get(k)
}

// 设置单例对象
func Set(k string, v interface{}) {
    instances.Set(k, v)
}

// 自定义框架核心组件：View
func SetView(v *gview.View) {
    instances.Set(gFRAME_CORE_COMPONENT_NAME_VIEW, v)
}

// 自定义框架核心组件：Config
func SetConfig(v *gcfg.Config) {
    instances.Set(gFRAME_CORE_COMPONENT_NAME_CONFIG, v)
}

// 自定义框架核心组件：Database
func SetDatabase(v gdb.Link, names...string) {
    dbCacheKey := gFRAME_CORE_COMPONENT_NAME_DATABASE
    if len(names) > 0 {
        dbCacheKey += names[0]
    }
    instances.Set(dbCacheKey, v)
}

// 核心对象：View
func View() *gview.View {
    result := Get(gFRAME_CORE_COMPONENT_NAME_VIEW)
    if result != nil {
        return result.(*gview.View)
    } else {
        path := gcmd.Option.Get("viewpath")
        if path == "" {
            path = genv.Get("viewpath")
            if path == "" {
                path = gfile.SelfDir()
            }
        }
        view := gview.Get(path)
        Set(gFRAME_CORE_COMPONENT_NAME_VIEW, view)
        return view
    }
    return nil
}

// 核心对象：Config
// 配置文件目录查找依次为：启动参数cfgpath、当前程序运行目录
func Config() *gcfg.Config {
    result := Get(gFRAME_CORE_COMPONENT_NAME_CONFIG)
    if result != nil {
        return result.(*gcfg.Config)
    } else {
        path := gcmd.Option.Get("cfgpath")
        if path == "" {
            path = genv.Get("cfgpath")
            if path == "" {
                path = gfile.SelfDir()
            }
        }
        config := gcfg.New(path)
        Set(gFRAME_CORE_COMPONENT_NAME_CONFIG, config)
        return config
    }
    return nil
}

// 核心对象：Database
func Database(names...string) *gdb.Db {
    dbCacheKey := gFRAME_CORE_COMPONENT_NAME_DATABASE
    if len(names) > 0 {
        dbCacheKey += names[0]
    }
    result := Get(dbCacheKey)
    if result != nil {
        return result.(*gdb.Db)
    } else {
        config := Config()
        if config == nil {
            return nil
        }
        if m := config.GetMap("database"); m != nil {
            for group, v := range m {
                if list, ok := v.([]interface{}); ok {
                    for _, nodev := range list {
                        node  := gdb.ConfigNode{}
                        nodem := nodev.(map[string]interface{})
                        if value, ok := nodem["host"]; ok {
                            node.Host = value.(string)
                        }
                        if value, ok := nodem["port"]; ok {
                            node.Port = value.(string)
                        }
                        if value, ok := nodem["user"]; ok {
                            node.User = value.(string)
                        }
                        if value, ok := nodem["pass"]; ok {
                            node.Pass = value.(string)
                        }
                        if value, ok := nodem["name"]; ok {
                            node.Name = value.(string)
                        }
                        if value, ok := nodem["type"]; ok {
                            node.Type = value.(string)
                        }
                        if value, ok := nodem["role"]; ok {
                            node.Role = value.(string)
                        }
                        if value, ok := nodem["charset"]; ok {
                            node.Charset = value.(string)
                        }
                        if value, ok := nodem["priority"]; ok {
                            node.Priority, _ = strconv.Atoi(value.(string))
                        }
                        gdb.AddConfigNode(group, node)
                    }
                }
            }

            if db, err := gdb.Instance(names...); err == nil {
                Set(dbCacheKey, db)
                return db
            } else {
                return nil
            }
        }
    }
    return nil
}<commit_msg>修复gins.SetDatabase方法参数<commit_after>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// 单例对象管理.
// 框架内置了一些核心对象获取方法，并且可以通过Set和Get方法实现IoC以及对内置核心对象的自定义替换
package gins

import (
    "strconv"
    "gitee.com/johng/gf/g/os/gcfg"
    "gitee.com/johng/gf/g/os/gcmd"
    "gitee.com/johng/gf/g/os/genv"
    "gitee.com/johng/gf/g/os/gview"
    "gitee.com/johng/gf/g/os/gfile"
    "gitee.com/johng/gf/g/database/gdb"
    "gitee.com/johng/gf/g/container/gmap"
)

const (
    gFRAME_CORE_COMPONENT_NAME_VIEW       = "gf.core.component.view"
    gFRAME_CORE_COMPONENT_NAME_CONFIG     = "gf.core.component.config"
    gFRAME_CORE_COMPONENT_NAME_DATABASE   = "gf.core.component.database"
)

// 单例对象存储器
var instances = gmap.NewStringInterfaceMap()

// 获取单例对象
func Get(k string) interface{} {
    return instances.Get(k)
}

// 设置单例对象
func Set(k string, v interface{}) {
    instances.Set(k, v)
}

// 自定义框架核心组件：View
func SetView(v *gview.View) {
    instances.Set(gFRAME_CORE_COMPONENT_NAME_VIEW, v)
}

// 自定义框架核心组件：Config
func SetConfig(v *gcfg.Config) {
    instances.Set(gFRAME_CORE_COMPONENT_NAME_CONFIG, v)
}

// 自定义框架核心组件：Database
func SetDatabase(v *gdb.Db, names...string) {
    dbCacheKey := gFRAME_CORE_COMPONENT_NAME_DATABASE
    if len(names) > 0 {
        dbCacheKey += names[0]
    }
    instances.Set(dbCacheKey, v)
}

// 核心对象：View
func View() *gview.View {
    result := Get(gFRAME_CORE_COMPONENT_NAME_VIEW)
    if result != nil {
        return result.(*gview.View)
    } else {
        path := gcmd.Option.Get("viewpath")
        if path == "" {
            path = genv.Get("viewpath")
            if path == "" {
                path = gfile.SelfDir()
            }
        }
        view := gview.Get(path)
        Set(gFRAME_CORE_COMPONENT_NAME_VIEW, view)
        return view
    }
    return nil
}

// 核心对象：Config
// 配置文件目录查找依次为：启动参数cfgpath、当前程序运行目录
func Config() *gcfg.Config {
    result := Get(gFRAME_CORE_COMPONENT_NAME_CONFIG)
    if result != nil {
        return result.(*gcfg.Config)
    } else {
        path := gcmd.Option.Get("cfgpath")
        if path == "" {
            path = genv.Get("cfgpath")
            if path == "" {
                path = gfile.SelfDir()
            }
        }
        config := gcfg.New(path)
        Set(gFRAME_CORE_COMPONENT_NAME_CONFIG, config)
        return config
    }
    return nil
}

// 核心对象：Database
func Database(names...string) *gdb.Db {
    dbCacheKey := gFRAME_CORE_COMPONENT_NAME_DATABASE
    if len(names) > 0 {
        dbCacheKey += names[0]
    }
    result := Get(dbCacheKey)
    if result != nil {
        return result.(*gdb.Db)
    } else {
        config := Config()
        if config == nil {
            return nil
        }
        if m := config.GetMap("database"); m != nil {
            for group, v := range m {
                if list, ok := v.([]interface{}); ok {
                    for _, nodev := range list {
                        node  := gdb.ConfigNode{}
                        nodem := nodev.(map[string]interface{})
                        if value, ok := nodem["host"]; ok {
                            node.Host = value.(string)
                        }
                        if value, ok := nodem["port"]; ok {
                            node.Port = value.(string)
                        }
                        if value, ok := nodem["user"]; ok {
                            node.User = value.(string)
                        }
                        if value, ok := nodem["pass"]; ok {
                            node.Pass = value.(string)
                        }
                        if value, ok := nodem["name"]; ok {
                            node.Name = value.(string)
                        }
                        if value, ok := nodem["type"]; ok {
                            node.Type = value.(string)
                        }
                        if value, ok := nodem["role"]; ok {
                            node.Role = value.(string)
                        }
                        if value, ok := nodem["charset"]; ok {
                            node.Charset = value.(string)
                        }
                        if value, ok := nodem["priority"]; ok {
                            node.Priority, _ = strconv.Atoi(value.(string))
                        }
                        gdb.AddConfigNode(group, node)
                    }
                }
            }

            if db, err := gdb.Instance(names...); err == nil {
                Set(dbCacheKey, db)
                return db
            } else {
                return nil
            }
        }
    }
    return nil
}<|endoftext|>


<commit_before>package gaerecords

import (
	"appengine/datastore"
)

type RecordFields map[string]interface{}

type RecordID int64

/*
	Record
	------------------------------------------------------------
*/
type Record struct {

	recordID RecordID
	parent *Record

	Manager *RecordManager
	Fields RecordFields

}

// Creates a new record
func NewRecord() *Record {
	r := new(Record)
	r.Fields = make(RecordFields)
	r.recordID = -1
	return r
}

/*
	Fields
*/

// Sets a field in the record
func (r *Record) Set(k string, v interface{}) *Record {
	r.Fields[k] = v
	return r
}

// Gets the value of a field in a record
func (r *Record) Get(k string) interface{} {
	return r.Fields[k]
}

/*
	ID Management
*/

// Gets the ID for this record
func (r *Record) ID() RecordID {
	return r.recordID
}

// Sets the ID for this record
func (r *Record) setID(id RecordID) *Record {
	r.recordID = id
	return r
}

// Whether this record has been persisted in the
// datastore or not
func (r *Record) IsPersisted() bool {
	return r.recordID > -1
}

/*
	Parentage
*/

func (r *Record) SetParent(parent *Record) *Record {
	r.parent = parent
	return r
}

func (r *Record) Parent() *Record {
	return r.parent
}

// TODO: test me
func (r *Record) HasParent() bool {
	return r.Parent() != nil
}

/*
	Persistence
*/

// Gets the datastore key for this record
func (r *Record) GetDatastoreKey() *datastore.Key {

	var key *datastore.Key
	var parentKey *datastore.Key

	if r.HasParent() {
		parentKey = r.Parent().GetDatastoreKey()
	}

	if r.IsPersisted() {
		key = datastore.NewKey(r.Manager.appengineContext, r.Manager.RecordType(), "", int64(r.ID()), parentKey)
	} else {
		key = datastore.NewIncompleteKey(r.Manager.appengineContext, r.Manager.RecordType(), parentKey)
	}

	return key

}

<commit_msg>added some better doc for record<commit_after>package gaerecords

import (
	"appengine/datastore"
)

// A map of the fields of a record
type RecordFields map[string]interface{}

// The type of IDs used to uniquely identify records
type RecordID int64

// Represents a single record
type Record struct {

	recordID RecordID
	parent *Record

	Manager *RecordManager
	Fields RecordFields

}

// Creates a new record
func NewRecord() *Record {
	r := new(Record)
	r.Fields = make(RecordFields)
	r.recordID = -1
	return r
}

/*
	Fields
*/

// Sets a field in the record
func (r *Record) Set(k string, v interface{}) *Record {
	r.Fields[k] = v
	return r
}

// Gets the value of a field in a record
func (r *Record) Get(k string) interface{} {
	return r.Fields[k]
}

/*
	ID Management
*/

// Gets the ID for this record
func (r *Record) ID() RecordID {
	return r.recordID
}

// Sets the ID for this record
func (r *Record) setID(id RecordID) *Record {
	r.recordID = id
	return r
}

// Whether this record has been persisted in the
// datastore or not
func (r *Record) IsPersisted() bool {
	return r.recordID > -1
}

/*
	Parentage
*/

func (r *Record) SetParent(parent *Record) *Record {
	r.parent = parent
	return r
}

func (r *Record) Parent() *Record {
	return r.parent
}

// TODO: test me
func (r *Record) HasParent() bool {
	return r.Parent() != nil
}

/*
	Persistence
*/

// Gets the datastore key for this record
func (r *Record) GetDatastoreKey() *datastore.Key {

	var key *datastore.Key
	var parentKey *datastore.Key

	if r.HasParent() {
		parentKey = r.Parent().GetDatastoreKey()
	}

	if r.IsPersisted() {
		key = datastore.NewKey(r.Manager.appengineContext, r.Manager.RecordType(), "", int64(r.ID()), parentKey)
	} else {
		key = datastore.NewIncompleteKey(r.Manager.appengineContext, r.Manager.RecordType(), parentKey)
	}

	return key

}

<|endoftext|>


<commit_before>package gcm

import (
	"github.com/alexjlockwood/gcm"
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server"
	"github.com/smancke/guble/store"

	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
)

// GCM_REGISTRATIONS_SCHEMA is the default sqlite schema for gcm
const GCM_REGISTRATIONS_SCHEMA = "gcm_registration"

// GCMConnector is the structure for handling the communication with Google Cloud Messaging
type GCMConnector struct {
	router             server.Router
	kvStore            store.KVStore
	prefix             string
	channelFromRouter  chan server.MsgAndRoute
	closeRouteByRouter chan server.Route
	stopChan           chan bool
	sender             *gcm.Sender
}

// NewGCMConnector creates a new gcmConnector without starting it
func NewGCMConnector(router server.Router, prefix string, gcmAPIKey string) (*GCMConnector, error) {

	kvStore, err := router.KVStore()
	if err != nil {
		return nil, err
	}

	gcm := &GCMConnector{
		router:            router,
		kvStore:           kvStore,
		prefix:            prefix,
		channelFromRouter: make(chan server.MsgAndRoute, 1000),
		stopChan:          make(chan bool, 1),
		sender:            &gcm.Sender{ApiKey: gcmAPIKey},
	}

	return gcm, nil
}

// Start opens the connector and awaits for messages from router to be forwarded to gcm until the stop signal is emitted
func (conn *GCMConnector) Start() error {
	broadcastRoute := server.NewRoute(removeTrailingSlash(conn.prefix)+"/broadcast", conn.channelFromRouter, "gcm_connector", "gcm_connector")
	conn.router.Subscribe(broadcastRoute)
	go func() {
		conn.loadSubscriptions()

		for {
			select {
			case msg := <-conn.channelFromRouter:
				if string(msg.Message.Path) == removeTrailingSlash(conn.prefix)+"/broadcast" {
					go conn.broadcastMessage(msg)
				} else {
					go conn.sendMessage(msg)
				}
			case <-conn.stopChan:
				return
			}
		}
	}()
	return nil
}

func (conn *GCMConnector) Check() error {
	return nil
}

func (conn *GCMConnector) sendMessage(msg server.MsgAndRoute) {
	gcmID := msg.Route.ApplicationID

	payload := conn.parseMessageToMap(msg.Message)

	var messageToGcm = gcm.NewMessage(payload, gcmID)
	protocol.Info("sending message to %v ...", gcmID)
	result, err := conn.sender.Send(messageToGcm, 5)
	if err != nil {
		protocol.Err("error sending message to gcm gcmID=%v: %v", gcmID, err.Error())
		return
	}

	errorJSON := result.Results[0].Error
	if errorJSON != "" {
		conn.handleJSONError(errorJSON, gcmID, msg.Route)
	} else {
		protocol.Debug("delivered message to gcm gcmID=%v: %v", gcmID, errorJSON)
	}

	// we only send to one receiver,
	// so we know that we can replace the old id with the first registration id (=canonical id)
	if result.CanonicalIDs != 0 {
		conn.replaceSubscriptionWithCanonicalID(msg.Route, result.Results[0].RegistrationID)
	}
}

func (conn *GCMConnector) parseMessageToMap(msg *protocol.Message) map[string]interface{} {
	payload := map[string]interface{}{}
	if msg.Body[0] == '{' {
		json.Unmarshal(msg.Body, &payload)
	} else {
		payload["message"] = msg.BodyAsString()
	}
	protocol.Debug("parsed message is: %v", payload)
	return payload
}

func (conn *GCMConnector) broadcastMessage(msg server.MsgAndRoute) {
	topic := msg.Message.Path
	payload := conn.parseMessageToMap(msg.Message)
	protocol.Info("broadcasting message with topic %v ...", string(topic))

	subscriptions := conn.kvStore.Iterate(GCM_REGISTRATIONS_SCHEMA, "")
	count := 0
	for {
		select {
		case entry, ok := <-subscriptions:
			if !ok {
				protocol.Info("send message to %v receivers", count)
				return
			}
			gmcID := entry[0]
			//TODO collect 1000 gcmIds and send them in one request!
			broadcastMessage := gcm.NewMessage(payload, gmcID)
			go func() {
				//TODO error handling of response!
				_, err := conn.sender.Send(broadcastMessage, 3)
				protocol.Debug("sent broadcast message to gcmId=%v", gmcID)
				if err != nil {
					protocol.Err("error sending broadcast message to cgmid=%v: %v", gmcID, err.Error())
				}
			}()
			count++
		}
	}
}

func (conn *GCMConnector) replaceSubscriptionWithCanonicalID(route *server.Route, newGcmID string) {
	oldGcmID := route.ApplicationID
	topic := string(route.Path)
	userID := route.UserID

	protocol.Info("replacing old gcmId %v with canonicalId %v", oldGcmID, newGcmID)

	conn.removeSubscription(route, oldGcmID)
	conn.subscribe(topic, userID, newGcmID)
}

func (conn *GCMConnector) handleJSONError(jsonError string, gcmID string, route *server.Route) {
	if jsonError == "NotRegistered" {
		protocol.Debug("remove not registered cgm registration cgmid=%v", gcmID)
		conn.removeSubscription(route, gcmID)
	} else if jsonError == "InvalidRegistration" {
		protocol.Err("the cgmid=%v is not registered. %v", gcmID, jsonError)
	} else {
		protocol.Err("unexpected error while sending to cgm cgmid=%v: %v", gcmID, jsonError)
	}
}

// Stop signals the closing of gcmConnector
func (conn *GCMConnector) Stop() error {
	conn.stopChan <- true
	return nil
}

// GetPrefix is used to satisfy the HTTP handler interface
func (conn *GCMConnector) GetPrefix() string {
	return conn.prefix
}

func (conn *GCMConnector) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		protocol.Err("Only HTTP POST METHOD SUPPORTED but received type=[%s]", r.Method)
		http.Error(w, "Permission Denied", http.StatusMethodNotAllowed)
		return
	}

	userID, gcmID, topic, err := conn.parseParams(r.URL.Path)
	if err != nil {
		http.Error(w, "Invalid Parameters in request", http.StatusBadRequest)
		return
	}
	conn.subscribe(topic, userID, gcmID)

	fmt.Fprintf(w, "registered: %v\n", topic)
}

// parseParams will parse the HTTP URL with format /gcm/:userid/:gcmid/subscribe/*topic
// returning error if the request is not in the corect format   or else the parsed Params
func (conn *GCMConnector) parseParams(path string) (userID, gcmID, topic string, err error) {
	subscribePrefixPath := "subscribe"
	currentURLPath := removeTrailingSlash(path)

	if strings.HasPrefix(currentURLPath, conn.prefix) != true {
		err = errors.New("Gcm request is not starting with gcm prefix")
		return
	}
	pathAfterPrefix := strings.TrimPrefix(currentURLPath, conn.prefix)

	splitParams := strings.SplitN(pathAfterPrefix, "/", 3)
	if len(splitParams) != 3 {
		err = errors.New("Gcm request has wrong number of params")
		return
	}
	userID = splitParams[0]
	gcmID = splitParams[1]

	if strings.HasPrefix(splitParams[2], subscribePrefixPath+"/") != true {
		err = errors.New("Gcm request third param is not subscribe")
		return
	}
	topic = strings.TrimPrefix(splitParams[2], subscribePrefixPath)
	return userID, gcmID, topic, nil
}

func (conn *GCMConnector) subscribe(topic string, userID string, gcmID string) {
	protocol.Info("gcm connector registration to userid=%q, gcmid=%q: %q", userID, gcmID, topic)

	route := server.NewRoute(topic, conn.channelFromRouter, gcmID, userID)

	conn.router.Subscribe(route)
	conn.saveSubscription(userID, topic, gcmID)
}

func (conn *GCMConnector) removeSubscription(route *server.Route, gcmID string) {
	conn.router.Unsubscribe(route)
	conn.kvStore.Delete(GCM_REGISTRATIONS_SCHEMA, gcmID)
}

func (conn *GCMConnector) saveSubscription(userID, topic, gcmID string) {
	conn.kvStore.Put(GCM_REGISTRATIONS_SCHEMA, gcmID, []byte(userID+":"+topic))
}

func (conn *GCMConnector) loadSubscriptions() {
	subscriptions := conn.kvStore.Iterate(GCM_REGISTRATIONS_SCHEMA, "")
	count := 0
	for {
		select {
		case entry, ok := <-subscriptions:
			if !ok {
				protocol.Info("renewed %v gcm subscriptions", count)
				return
			}
			gcmID := entry[0]
			splitValue := strings.SplitN(entry[1], ":", 2)
			userID := splitValue[0]
			topic := splitValue[1]

			protocol.Debug("renew gcm subscription: user=%v, topic=%v, gcmid=%v", userID, topic, gcmID)
			route := server.NewRoute(topic, conn.channelFromRouter, gcmID, userID)
			conn.router.Subscribe(route)
			count++
		}
	}
}

func removeTrailingSlash(path string) string {
	if len(path) > 1 && path[len(path)-1] == '/' {
		return path[:len(path)-1]
	}
	return path
}
<commit_msg>minor refactoring<commit_after>package gcm

import (
	"github.com/alexjlockwood/gcm"
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server"
	"github.com/smancke/guble/store"

	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
)

// GCM_REGISTRATIONS_SCHEMA is the default sqlite schema for gcm
const GCM_REGISTRATIONS_SCHEMA = "gcm_registration"

// GCMConnector is the structure for handling the communication with Google Cloud Messaging
type GCMConnector struct {
	router             server.Router
	kvStore            store.KVStore
	prefix             string
	channelFromRouter  chan server.MsgAndRoute
	closeRouteByRouter chan server.Route
	stopChan           chan bool
	sender             *gcm.Sender
}

// NewGCMConnector creates a new gcmConnector without starting it
func NewGCMConnector(router server.Router, prefix string, gcmAPIKey string) (*GCMConnector, error) {

	kvStore, err := router.KVStore()
	if err != nil {
		return nil, err
	}

	gcm := &GCMConnector{
		router:            router,
		kvStore:           kvStore,
		prefix:            prefix,
		channelFromRouter: make(chan server.MsgAndRoute, 1000),
		stopChan:          make(chan bool, 1),
		sender:            &gcm.Sender{ApiKey: gcmAPIKey},
	}

	return gcm, nil
}

// Start opens the connector and awaits for messages from router to be forwarded to gcm until the stop signal is emitted
func (conn *GCMConnector) Start() error {
	broadcastRoute := server.NewRoute(removeTrailingSlash(conn.prefix)+"/broadcast", conn.channelFromRouter, "gcm_connector", "gcm_connector")
	conn.router.Subscribe(broadcastRoute)
	go func() {
		conn.loadSubscriptions()

		for {
			select {
			case msg := <-conn.channelFromRouter:
				if string(msg.Message.Path) == removeTrailingSlash(conn.prefix)+"/broadcast" {
					go conn.broadcastMessage(msg)
				} else {
					go conn.sendMessage(msg)
				}
			case <-conn.stopChan:
				return
			}
		}
	}()
	return nil
}

func (conn *GCMConnector) Check() error {
	return nil
}

func (conn *GCMConnector) sendMessage(msg server.MsgAndRoute) {
	gcmID := msg.Route.ApplicationID

	payload := conn.parseMessageToMap(msg.Message)

	var messageToGcm = gcm.NewMessage(payload, gcmID)
	protocol.Info("sending message to %v ...", gcmID)
	result, err := conn.sender.Send(messageToGcm, 5)
	if err != nil {
		protocol.Err("error sending message to GCM gcmId=%v: %v", gcmID, err.Error())
		return
	}

	errorJSON := result.Results[0].Error
	if errorJSON != "" {
		conn.handleJSONError(errorJSON, gcmID, msg.Route)
	} else {
		protocol.Debug("delivered message to GCM gcmId=%v: %v", gcmID, errorJSON)
	}

	// we only send to one receiver,
	// so we know that we can replace the old id with the first registration id (=canonical id)
	if result.CanonicalIDs != 0 {
		conn.replaceSubscriptionWithCanonicalID(msg.Route, result.Results[0].RegistrationID)
	}
}

func (conn *GCMConnector) parseMessageToMap(msg *protocol.Message) map[string]interface{} {
	payload := map[string]interface{}{}
	if msg.Body[0] == '{' {
		json.Unmarshal(msg.Body, &payload)
	} else {
		payload["message"] = msg.BodyAsString()
	}
	protocol.Debug("parsed message is: %v", payload)
	return payload
}

func (conn *GCMConnector) broadcastMessage(msg server.MsgAndRoute) {
	topic := msg.Message.Path
	payload := conn.parseMessageToMap(msg.Message)
	protocol.Info("broadcasting message with topic %v ...", string(topic))

	subscriptions := conn.kvStore.Iterate(GCM_REGISTRATIONS_SCHEMA, "")
	count := 0
	for {
		select {
		case entry, ok := <-subscriptions:
			if !ok {
				protocol.Info("send message to %v receivers", count)
				return
			}
			gmcID := entry[0]
			//TODO collect 1000 gcmIds and send them in one request!
			broadcastMessage := gcm.NewMessage(payload, gmcID)
			go func() {
				//TODO error handling of response!
				_, err := conn.sender.Send(broadcastMessage, 3)
				protocol.Debug("sent broadcast message to gcmId=%v", gmcID)
				if err != nil {
					protocol.Err("error sending broadcast message to gcmId=%v: %v", gmcID, err.Error())
				}
			}()
			count++
		}
	}
}

func (conn *GCMConnector) replaceSubscriptionWithCanonicalID(route *server.Route, newGcmID string) {
	oldGcmID := route.ApplicationID
	topic := string(route.Path)
	userID := route.UserID

	protocol.Info("replacing old gcmId %v with canonicalId %v", oldGcmID, newGcmID)

	conn.removeSubscription(route, oldGcmID)
	conn.subscribe(topic, userID, newGcmID)
}

func (conn *GCMConnector) handleJSONError(jsonError string, gcmID string, route *server.Route) {
	if jsonError == "NotRegistered" {
		protocol.Debug("remove not registered cgm registration gcmid=%v", gcmID)
		conn.removeSubscription(route, gcmID)
	} else if jsonError == "InvalidRegistration" {
		protocol.Err("the cgmid=%v is not registered. %v", gcmID, jsonError)
	} else {
		protocol.Err("unexpected error while sending to cgm gcmId=%v: %v", gcmID, jsonError)
	}
}

// Stop signals the closing of gcmConnector
func (conn *GCMConnector) Stop() error {
	conn.stopChan <- true
	return nil
}

// GetPrefix is used to satisfy the HTTP handler interface
func (conn *GCMConnector) GetPrefix() string {
	return conn.prefix
}

func (conn *GCMConnector) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		protocol.Err("Only HTTP POST METHOD SUPPORTED but received type=[%s]", r.Method)
		http.Error(w, "Permission Denied", http.StatusMethodNotAllowed)
		return
	}

	userID, gcmID, topic, err := conn.parseParams(r.URL.Path)
	if err != nil {
		http.Error(w, "Invalid Parameters in request", http.StatusBadRequest)
		return
	}
	conn.subscribe(topic, userID, gcmID)

	fmt.Fprintf(w, "registered: %v\n", topic)
}

// parseParams will parse the HTTP URL with format /gcm/:userid/:gcmid/subscribe/*topic
// returning the parsed Params, or error if the request is not in the correct format
func (conn *GCMConnector) parseParams(path string) (userID, gcmID, topic string, err error) {
	subscribePrefixPath := "subscribe"
	currentURLPath := removeTrailingSlash(path)

	if strings.HasPrefix(currentURLPath, conn.prefix) != true {
		err = errors.New("Gcm request is not starting with gcm prefix")
		return
	}
	pathAfterPrefix := strings.TrimPrefix(currentURLPath, conn.prefix)

	splitParams := strings.SplitN(pathAfterPrefix, "/", 3)
	if len(splitParams) != 3 {
		err = errors.New("Gcm request has wrong number of params")
		return
	}
	userID = splitParams[0]
	gcmID = splitParams[1]

	if strings.HasPrefix(splitParams[2], subscribePrefixPath+"/") != true {
		err = errors.New("Gcm request third param is not subscribe")
		return
	}
	topic = strings.TrimPrefix(splitParams[2], subscribePrefixPath)
	return userID, gcmID, topic, nil
}

func (conn *GCMConnector) subscribe(topic string, userID string, gcmID string) {
	protocol.Info("gcm connector registration to userid=%q, gcmid=%q: %q", userID, gcmID, topic)

	route := server.NewRoute(topic, conn.channelFromRouter, gcmID, userID)

	conn.router.Subscribe(route)
	conn.saveSubscription(userID, topic, gcmID)
}

func (conn *GCMConnector) removeSubscription(route *server.Route, gcmID string) {
	conn.router.Unsubscribe(route)
	conn.kvStore.Delete(GCM_REGISTRATIONS_SCHEMA, gcmID)
}

func (conn *GCMConnector) saveSubscription(userID, topic, gcmID string) {
	conn.kvStore.Put(GCM_REGISTRATIONS_SCHEMA, gcmID, []byte(userID+":"+topic))
}

func (conn *GCMConnector) loadSubscriptions() {
	subscriptions := conn.kvStore.Iterate(GCM_REGISTRATIONS_SCHEMA, "")
	count := 0
	for {
		select {
		case entry, ok := <-subscriptions:
			if !ok {
				protocol.Info("renewed %v gcm subscriptions", count)
				return
			}
			gcmID := entry[0]
			splitValue := strings.SplitN(entry[1], ":", 2)
			userID := splitValue[0]
			topic := splitValue[1]

			protocol.Debug("renew gcm subscription: user=%v, topic=%v, gcmid=%v", userID, topic, gcmID)
			route := server.NewRoute(topic, conn.channelFromRouter, gcmID, userID)
			conn.router.Subscribe(route)
			count++
		}
	}
}

func removeTrailingSlash(path string) string {
	if len(path) > 1 && path[len(path)-1] == '/' {
		return path[:len(path)-1]
	}
	return path
}
<|endoftext|>


<commit_before>/*
                       WARNING WARNING WARNING

                Attention all potential contributors

   This testfile is not in the best state. We've been slowly transitioning
   from the built in "testing" package to using Ginkgo. As you can see, we've
   changed the format, but a lot of the setup, test body, descriptions, etc
   are either hardcoded, completely lacking, or misleading.

   For example:

   Describe("Testing with ginkgo"...)      // This is not a great description
   It("TestDoesSoemthing"...)              // This is a horrible description

   Describe("create-user command"...       // Describe the actual object under test
   It("creates a user when provided ..."   // this is more descriptive

   For good examples of writing Ginkgo tests for the cli, refer to

   src/cf/commands/application/delete_app_test.go
   src/cf/terminal/ui_test.go
   src/github.com/cloudfoundry/loggregator_consumer/consumer_test.go
*/

package api_test

import (
	. "cf/api"
	"cf/errors"
	"cf/models"
	"cf/net"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"net/http"
	"net/http/httptest"
	testapi "testhelpers/api"
	testconfig "testhelpers/configuration"
	testnet "testhelpers/net"
)

var _ = Describe("Testing with ginkgo", func() {
	var defaultCreateRequestBodyMatcher testnet.RequestMatcher
	var deleteBindingReq testnet.TestRequest

	BeforeEach(func() {
		defaultCreateRequestBodyMatcher = testnet.RequestBodyMatcher(`{"app_guid":"my-app-guid","service_instance_guid":"my-service-instance-guid","async":true}`)
		deleteBindingReq = testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "DELETE",
			Path:     "/v2/service_bindings/service-binding-2-guid",
			Response: testnet.TestResponse{Status: http.StatusOK},
		})
	})

	It("TestCreateServiceBinding", func() {
		req := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "POST",
			Path:     "/v2/service_bindings",
			Matcher:  defaultCreateRequestBodyMatcher,
			Response: testnet.TestResponse{Status: http.StatusCreated},
		})

		ts, handler, repo := createServiceBindingRepo([]testnet.TestRequest{req})
		defer ts.Close()

		apiErr := repo.Create("my-service-instance-guid", "my-app-guid")
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
	})

	It("TestCreateServiceBindingIfError", func() {
		req := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:  "POST",
			Path:    "/v2/service_bindings",
			Matcher: defaultCreateRequestBodyMatcher,
			Response: testnet.TestResponse{
				Status: http.StatusBadRequest,
				Body:   `{"code":90003,"description":"The app space binding to service is taken: 7b959018-110a-4913-ac0a-d663e613cdea 346bf237-7eef-41a7-b892-68fb08068f09"}`,
			},
		})

		ts, handler, repo := createServiceBindingRepo([]testnet.TestRequest{req})
		defer ts.Close()

		apiErr := repo.Create("my-service-instance-guid", "my-app-guid")

		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(BeNil())
		Expect(apiErr.(errors.HttpError).ErrorCode()).To(Equal("90003"))
	})

	It("TestDeleteServiceBinding", func() {
		ts, handler, repo := createServiceBindingRepo([]testnet.TestRequest{deleteBindingReq})
		defer ts.Close()

		serviceInstance := models.ServiceInstance{}
		serviceInstance.Guid = "my-service-instance-guid"

		binding := models.ServiceBindingFields{}
		binding.Url = "/v2/service_bindings/service-binding-1-guid"
		binding.AppGuid = "app-1-guid"
		binding2 := models.ServiceBindingFields{}
		binding2.Url = "/v2/service_bindings/service-binding-2-guid"
		binding2.AppGuid = "app-2-guid"
		serviceInstance.ServiceBindings = []models.ServiceBindingFields{binding, binding2}

		found, apiErr := repo.Delete(serviceInstance, "app-2-guid")

		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
		Expect(found).To(BeTrue())
	})

	It("TestDeleteServiceBindingWhenBindingDoesNotExist", func() {
		ts, handler, repo := createServiceBindingRepo([]testnet.TestRequest{})
		defer ts.Close()

		serviceInstance := models.ServiceInstance{}
		serviceInstance.Guid = "my-service-instance-guid"

		found, apiErr := repo.Delete(serviceInstance, "app-2-guid")

		Expect(handler.CallCount).To(Equal(0))
		Expect(apiErr).NotTo(HaveOccurred())
		Expect(found).To(BeFalse())
	})
})

func createServiceBindingRepo(requests []testnet.TestRequest) (ts *httptest.Server, handler *testnet.TestHandler, repo ServiceBindingRepository) {
	ts, handler = testnet.NewServer(requests)
	configRepo := testconfig.NewRepositoryWithDefaults()
	configRepo.SetApiEndpoint(ts.URL)
	gateway := net.NewCloudControllerGateway(configRepo)
	repo = NewCloudControllerServiceBindingRepository(configRepo, gateway)
	return
}
<commit_msg>refactor service bindings test<commit_after>package api_test

import (
	. "cf/api"
	"cf/configuration"
	"cf/errors"
	"cf/models"
	"cf/net"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"net/http"
	"net/http/httptest"
	testapi "testhelpers/api"
	testconfig "testhelpers/configuration"
	testnet "testhelpers/net"
)

var _ = Describe("Testing with ginkgo", func() {
	var (
		testServer  *httptest.Server
		testHandler *testnet.TestHandler
		configRepo  configuration.ReadWriter
		repo        CloudControllerServiceBindingRepository
	)

	setupTestServer := func(reqs ...testnet.TestRequest) {
		testServer, testHandler = testnet.NewServer(reqs)
		configRepo.SetApiEndpoint(testServer.URL)
	}

	BeforeEach(func() {
		configRepo = testconfig.NewRepositoryWithDefaults()

		gateway := net.NewCloudControllerGateway(configRepo)
		repo = NewCloudControllerServiceBindingRepository(configRepo, gateway)
	})

	AfterEach(func() {
		testServer.Close()
	})

	Describe("Create", func() {
		Context("when the service binding can be created", func() {
			BeforeEach(func() {
				setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "POST",
					Path:     "/v2/service_bindings",
					Matcher:  testnet.RequestBodyMatcher(`{"app_guid":"my-app-guid","service_instance_guid":"my-service-instance-guid","async":true}`),
					Response: testnet.TestResponse{Status: http.StatusCreated},
				}))
			})

			It("TestCreateServiceBinding", func() {
				apiErr := repo.Create("my-service-instance-guid", "my-app-guid")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(apiErr).NotTo(HaveOccurred())
			})
		})

		Context("when an error occurs", func() {
			BeforeEach(func() {
				setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:  "POST",
					Path:    "/v2/service_bindings",
					Matcher: testnet.RequestBodyMatcher(`{"app_guid":"my-app-guid","service_instance_guid":"my-service-instance-guid","async":true}`),
					Response: testnet.TestResponse{
						Status: http.StatusBadRequest,
						Body:   `{"code":90003,"description":"The app space binding to service is taken: 7b959018-110a-4913-ac0a-d663e613cdea 346bf237-7eef-41a7-b892-68fb08068f09"}`,
					},
				}))
			})

			It("TestCreateServiceBindingIfError", func() {
				apiErr := repo.Create("my-service-instance-guid", "my-app-guid")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(apiErr).NotTo(BeNil())
				Expect(apiErr.(errors.HttpError).ErrorCode()).To(Equal("90003"))
			})
		})
	})

	Describe("Delete", func() {
		Context("when binding does exist", func() {
			var serviceInstance models.ServiceInstance

			BeforeEach(func() {
				setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "DELETE",
					Path:     "/v2/service_bindings/service-binding-2-guid",
					Response: testnet.TestResponse{Status: http.StatusOK},
				}))

				serviceInstance.Guid = "my-service-instance-guid"

				binding := models.ServiceBindingFields{}
				binding.Url = "/v2/service_bindings/service-binding-1-guid"
				binding.AppGuid = "app-1-guid"
				binding2 := models.ServiceBindingFields{}
				binding2.Url = "/v2/service_bindings/service-binding-2-guid"
				binding2.AppGuid = "app-2-guid"
				serviceInstance.ServiceBindings = []models.ServiceBindingFields{binding, binding2}
			})

			It("TestDeleteServiceBinding", func() {
				found, apiErr := repo.Delete(serviceInstance, "app-2-guid")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(apiErr).NotTo(HaveOccurred())
				Expect(found).To(BeTrue())
			})
		})

		Context("when binding does not exist", func() {
			var serviceInstance models.ServiceInstance

			BeforeEach(func() {
				setupTestServer()
				serviceInstance.Guid = "my-service-instance-guid"
			})

			It("does not return an error", func() {
				found, apiErr := repo.Delete(serviceInstance, "app-2-guid")

				Expect(testHandler.CallCount).To(Equal(0))
				Expect(apiErr).NotTo(HaveOccurred())
				Expect(found).To(BeFalse())
			})
		})
	})
})
<|endoftext|>


<commit_before>package teams

import (
	"fmt"
	"sort"

	"github.com/keybase/client/go/kbfs"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"golang.org/x/net/context"
)

type implicitTeamConflict struct {
	// Note this TeamID is not validated by LookupImplicitTeam. Be aware of server trust.
	TeamID       keybase1.TeamID `json:"team_id"`
	Generation   int             `json:"generation"`
	ConflictDate string          `json:"conflict_date"`
}

func (i *implicitTeamConflict) parse() (*keybase1.ImplicitTeamConflictInfo, error) {
	return libkb.ParseImplicitTeamDisplayNameSuffix(fmt.Sprintf("(conflicted copy %s #%d)", i.ConflictDate, i.Generation))
}

type implicitTeam struct {
	TeamID      keybase1.TeamID        `json:"team_id"`
	DisplayName string                 `json:"display_name"`
	Private     bool                   `json:"is_private"`
	Conflicts   []implicitTeamConflict `json:"conflicts,omitempty"`
	Status      libkb.AppStatus        `json:"status"`
}

func (i *implicitTeam) GetAppStatus() *libkb.AppStatus {
	return &i.Status
}

// Lookup an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Resolves social assertions.
func LookupImplicitTeam(ctx context.Context, g *libkb.GlobalContext, displayName string, public bool) (
	teamID keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, err error) {

	teamID, teamName, impTeamName, tlfID, _, err = LookupImplicitTeamAndConflicts(ctx, g, displayName, public)
	return teamID, teamName, impTeamName, tlfID, err
}

// Lookup an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Resolves social assertions.
func LookupImplicitTeamAndConflicts(ctx context.Context, g *libkb.GlobalContext, displayName string, public bool) (
	teamID keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, conflicts []keybase1.ImplicitTeamConflictInfo, err error) {
	impName, err := ResolveImplicitTeamDisplayName(ctx, g, displayName, public)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	return lookupImplicitTeamAndConflicts(ctx, g, displayName, impName)
}

// Lookup an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Does not resolve social assertions.
// preResolveDisplayName is used for logging and errors
func lookupImplicitTeamAndConflicts(ctx context.Context, g *libkb.GlobalContext,
	preResolveDisplayName string, impTeamNameInput keybase1.ImplicitTeamDisplayName) (
	teamID keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, conflicts []keybase1.ImplicitTeamConflictInfo, err error) {

	defer g.CTraceTimed(ctx, fmt.Sprintf("lookupImplicitTeamAndConflicts(%v)", preResolveDisplayName), func() error { return err })()

	impTeamName = impTeamNameInput

	// Use a copy without the conflict info to hit the api endpoint
	var impTeamNameWithoutConflict keybase1.ImplicitTeamDisplayName
	impTeamNameWithoutConflict = impTeamName
	impTeamNameWithoutConflict.ConflictInfo = nil
	lookupNameWithoutConflict, err := FormatImplicitTeamDisplayName(ctx, g, impTeamNameWithoutConflict)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}

	arg := libkb.NewAPIArgWithNetContext(ctx, "team/implicit")
	arg.SessionType = libkb.APISessionTypeOPTIONAL
	arg.Args = libkb.HTTPArgs{
		"display_name": libkb.S{Val: lookupNameWithoutConflict},
		"public":       libkb.B{Val: impTeamName.IsPublic},
	}
	var imp implicitTeam
	if err = g.API.GetDecode(arg, &imp); err != nil {
		if aerr, ok := err.(libkb.AppStatusError); ok {
			code := keybase1.StatusCode(aerr.Code)
			switch code {
			case keybase1.StatusCode_SCTeamReadError:
				return teamID, teamName, impTeamName, tlfID, conflicts, NewTeamDoesNotExistError(
					impTeamName.IsPublic, preResolveDisplayName)
			case keybase1.StatusCode_SCTeamProvisionalCanKey, keybase1.StatusCode_SCTeamProvisionalCannotKey:
				return teamID, teamName, impTeamName, tlfID, conflicts, libkb.NewTeamProvisionalError(
					(code == keybase1.StatusCode_SCTeamProvisionalCanKey), impTeamName.IsPublic, preResolveDisplayName)
			}
		}
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	if len(imp.Conflicts) > 0 {
		g.Log.CDebugf(ctx, "LookupImplicitTeam found %v conflicts", len(imp.Conflicts))
	}
	// We will use this team. Changed later if we selected a conflict.
	var foundSelectedConflict bool
	teamID = imp.TeamID
	for _, conflict := range imp.Conflicts {
		conflictInfo, err := conflict.parse()
		if err != nil {
			// warn, don't fail
			g.Log.CWarningf(ctx, "LookupImplicitTeam got conflict suffix: %v", err)
		} else {
			conflicts = append(conflicts, *conflictInfo)
		}
		if impTeamName.ConflictInfo != nil {
			match := libkb.FormatImplicitTeamDisplayNameSuffix(*impTeamName.ConflictInfo) == libkb.FormatImplicitTeamDisplayNameSuffix(*conflictInfo)
			if match {
				teamID = conflict.TeamID
				foundSelectedConflict = true
			}
		}
	}
	if impTeamName.ConflictInfo != nil && !foundSelectedConflict {
		// We got the team but didn't find the specific conflict requested.
		return teamID, teamName, impTeamName, tlfID, conflicts, NewTeamDoesNotExistError(
			impTeamName.IsPublic, "could not find team with suffix: %v", preResolveDisplayName)
	}
	team, err := Load(ctx, g, keybase1.LoadTeamArg{
		ID:          imp.TeamID,
		Public:      impTeamName.IsPublic,
		ForceRepoll: true,
	})
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}

	// Check the display names. This is how we make sure the server returned a team with the right members.
	teamDisplayName, err := team.ImplicitTeamDisplayNameString(ctx)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	referenceImpName, err := FormatImplicitTeamDisplayName(ctx, g, impTeamNameWithoutConflict)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	if teamDisplayName != referenceImpName {
		return teamID, teamName, impTeamName, tlfID, conflicts, fmt.Errorf("implicit team name mismatch: %s != %s",
			teamDisplayName, referenceImpName)
	}
	if team.IsPublic() != impTeamName.IsPublic {
		return teamID, teamName, impTeamName, tlfID, conflicts, fmt.Errorf("implicit team public-ness mismatch: %v != %v", team.IsPublic(), impTeamName.IsPublic)
	}

	tlfID = team.KBFSTLFID()

	return teamID, team.Name(), impTeamName, tlfID, conflicts, nil
}

// Lookup or create an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Resolves social assertions.
func LookupOrCreateImplicitTeam(ctx context.Context, g *libkb.GlobalContext, displayName string, public bool) (res keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, err error) {
	defer g.CTraceTimed(ctx, fmt.Sprintf("LookupOrCreateImplicitTeam(%v)", displayName),
		func() error { return err })()
	lookupName, err := ResolveImplicitTeamDisplayName(ctx, g, displayName, public)
	if err != nil {
		return res, teamName, impTeamName, tlfID, err
	}

	res, teamName, impTeamName, tlfID, _, err = lookupImplicitTeamAndConflicts(ctx, g, displayName, lookupName)
	if err != nil {
		if _, ok := err.(TeamDoesNotExistError); ok {
			if lookupName.ConflictInfo != nil {
				// Don't create it if a conflict is specified.
				// Unlikely a caller would know the conflict info if it didn't exist.
				return res, teamName, impTeamName, tlfID, err
			}
			// If the team does not exist, then let's create it
			impTeamName = lookupName
			res, teamName, err = CreateImplicitTeam(ctx, g, impTeamName)
			return res, teamName, impTeamName, tlfID, err
		}
		return res, teamName, impTeamName, tlfID, err
	}
	return res, teamName, impTeamName, tlfID, nil
}

func FormatImplicitTeamDisplayName(ctx context.Context, g *libkb.GlobalContext, impTeamName keybase1.ImplicitTeamDisplayName) (string, error) {
	return formatImplicitTeamDisplayNameCommon(ctx, g, impTeamName, nil)
}

// Format an implicit display name, but order the specified username first in each of the writer and reader lists if it appears.
func FormatImplicitTeamDisplayNameWithUserFront(ctx context.Context, g *libkb.GlobalContext, impTeamName keybase1.ImplicitTeamDisplayName, frontName libkb.NormalizedUsername) (string, error) {
	return formatImplicitTeamDisplayNameCommon(ctx, g, impTeamName, &frontName)
}

func formatImplicitTeamDisplayNameCommon(ctx context.Context, g *libkb.GlobalContext, impTeamName keybase1.ImplicitTeamDisplayName, optionalFrontName *libkb.NormalizedUsername) (string, error) {
	var writerNames []string
	for _, u := range impTeamName.Writers.KeybaseUsers {
		writerNames = append(writerNames, u)
	}
	for _, u := range impTeamName.Writers.UnresolvedUsers {
		writerNames = append(writerNames, u.String())
	}
	if optionalFrontName == nil {
		sort.Strings(writerNames)
	} else {
		sortStringsFront(writerNames, optionalFrontName.String())
	}

	var readerNames []string
	for _, u := range impTeamName.Readers.KeybaseUsers {
		readerNames = append(readerNames, u)
	}
	for _, u := range impTeamName.Readers.UnresolvedUsers {
		readerNames = append(readerNames, u.String())
	}
	if optionalFrontName == nil {
		sort.Strings(readerNames)
	} else {
		sortStringsFront(readerNames, optionalFrontName.String())
	}

	var suffix string
	if impTeamName.ConflictInfo != nil && impTeamName.ConflictInfo.IsConflict() {
		suffix = libkb.FormatImplicitTeamDisplayNameSuffix(*impTeamName.ConflictInfo)
	}

	if len(writerNames) == 0 {
		return "", fmt.Errorf("invalid implicit team name: no writers")
	}

	return kbfs.NormalizeNamesInTLF(writerNames, readerNames, suffix)
}

// Sort a list of strings but order `front` in front IF it appears.
func sortStringsFront(ss []string, front string) {
	sort.Slice(ss, func(i, j int) bool {
		a := ss[i]
		b := ss[j]
		if a == front {
			return true
		}
		if b == front {
			return false
		}
		return a < b
	})
}
<commit_msg>maybe fix CI test that lookups up conflict info suffixes (#10151)<commit_after>package teams

import (
	"fmt"
	"sort"

	"github.com/keybase/client/go/kbfs"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"golang.org/x/net/context"
)

type implicitTeamConflict struct {
	// Note this TeamID is not validated by LookupImplicitTeam. Be aware of server trust.
	TeamID       keybase1.TeamID `json:"team_id"`
	Generation   int             `json:"generation"`
	ConflictDate string          `json:"conflict_date"`
}

func (i *implicitTeamConflict) parse() (*keybase1.ImplicitTeamConflictInfo, error) {
	return libkb.ParseImplicitTeamDisplayNameSuffix(fmt.Sprintf("(conflicted copy %s #%d)", i.ConflictDate, i.Generation))
}

type implicitTeam struct {
	TeamID      keybase1.TeamID        `json:"team_id"`
	DisplayName string                 `json:"display_name"`
	Private     bool                   `json:"is_private"`
	Conflicts   []implicitTeamConflict `json:"conflicts,omitempty"`
	Status      libkb.AppStatus        `json:"status"`
}

func (i *implicitTeam) GetAppStatus() *libkb.AppStatus {
	return &i.Status
}

// Lookup an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Resolves social assertions.
func LookupImplicitTeam(ctx context.Context, g *libkb.GlobalContext, displayName string, public bool) (
	teamID keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, err error) {

	teamID, teamName, impTeamName, tlfID, _, err = LookupImplicitTeamAndConflicts(ctx, g, displayName, public)
	return teamID, teamName, impTeamName, tlfID, err
}

// Lookup an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Resolves social assertions.
func LookupImplicitTeamAndConflicts(ctx context.Context, g *libkb.GlobalContext, displayName string, public bool) (
	teamID keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, conflicts []keybase1.ImplicitTeamConflictInfo, err error) {
	impName, err := ResolveImplicitTeamDisplayName(ctx, g, displayName, public)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	return lookupImplicitTeamAndConflicts(ctx, g, displayName, impName)
}

// Lookup an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Does not resolve social assertions.
// preResolveDisplayName is used for logging and errors
func lookupImplicitTeamAndConflicts(ctx context.Context, g *libkb.GlobalContext,
	preResolveDisplayName string, impTeamNameInput keybase1.ImplicitTeamDisplayName) (
	teamID keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, conflicts []keybase1.ImplicitTeamConflictInfo, err error) {

	defer g.CTraceTimed(ctx, fmt.Sprintf("lookupImplicitTeamAndConflicts(%v)", preResolveDisplayName), func() error { return err })()

	impTeamName = impTeamNameInput

	// Use a copy without the conflict info to hit the api endpoint
	var impTeamNameWithoutConflict keybase1.ImplicitTeamDisplayName
	impTeamNameWithoutConflict = impTeamName
	impTeamNameWithoutConflict.ConflictInfo = nil
	lookupNameWithoutConflict, err := FormatImplicitTeamDisplayName(ctx, g, impTeamNameWithoutConflict)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}

	arg := libkb.NewAPIArgWithNetContext(ctx, "team/implicit")
	arg.SessionType = libkb.APISessionTypeOPTIONAL
	arg.Args = libkb.HTTPArgs{
		"display_name": libkb.S{Val: lookupNameWithoutConflict},
		"public":       libkb.B{Val: impTeamName.IsPublic},
	}
	var imp implicitTeam
	if err = g.API.GetDecode(arg, &imp); err != nil {
		if aerr, ok := err.(libkb.AppStatusError); ok {
			code := keybase1.StatusCode(aerr.Code)
			switch code {
			case keybase1.StatusCode_SCTeamReadError:
				return teamID, teamName, impTeamName, tlfID, conflicts, NewTeamDoesNotExistError(
					impTeamName.IsPublic, preResolveDisplayName)
			case keybase1.StatusCode_SCTeamProvisionalCanKey, keybase1.StatusCode_SCTeamProvisionalCannotKey:
				return teamID, teamName, impTeamName, tlfID, conflicts, libkb.NewTeamProvisionalError(
					(code == keybase1.StatusCode_SCTeamProvisionalCanKey), impTeamName.IsPublic, preResolveDisplayName)
			}
		}
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	if len(imp.Conflicts) > 0 {
		g.Log.CDebugf(ctx, "LookupImplicitTeam found %v conflicts", len(imp.Conflicts))
	}
	// We will use this team. Changed later if we selected a conflict.
	var foundSelectedConflict bool
	teamID = imp.TeamID
	for i, conflict := range imp.Conflicts {
		g.Log.CDebugf(ctx, "| checking conflict: %+v (iter %d)", conflict, i)
		conflictInfo, err := conflict.parse()

		if err != nil {
			// warn, don't fail
			g.Log.CWarningf(ctx, "LookupImplicitTeam got conflict suffix: %v", err)
			err = nil
			continue
		}
		conflicts = append(conflicts, *conflictInfo)

		if conflictInfo == nil {
			g.Log.CDebugf(ctx, "| got unexpected nil conflictInfo (iter %d)", i)
			continue
		}

		g.Log.CDebugf(ctx, "| parsed conflict into conflictInfo: %+v", *conflictInfo)

		if impTeamName.ConflictInfo != nil {
			match := libkb.FormatImplicitTeamDisplayNameSuffix(*impTeamName.ConflictInfo) == libkb.FormatImplicitTeamDisplayNameSuffix(*conflictInfo)
			if match {
				teamID = conflict.TeamID
				foundSelectedConflict = true
				g.Log.CDebugf(ctx, "| found conflict suffix match: %v", teamID)
			} else {
				g.Log.CDebugf(ctx, "| conflict suffix didn't match (teamID %v)", conflict.TeamID)
			}
		}
	}
	if impTeamName.ConflictInfo != nil && !foundSelectedConflict {
		// We got the team but didn't find the specific conflict requested.
		return teamID, teamName, impTeamName, tlfID, conflicts, NewTeamDoesNotExistError(
			impTeamName.IsPublic, "could not find team with suffix: %v", preResolveDisplayName)
	}
	team, err := Load(ctx, g, keybase1.LoadTeamArg{
		ID:          teamID,
		Public:      impTeamName.IsPublic,
		ForceRepoll: true,
	})
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}

	// Check the display names. This is how we make sure the server returned a team with the right members.
	teamDisplayName, err := team.ImplicitTeamDisplayNameString(ctx)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	referenceImpName, err := FormatImplicitTeamDisplayName(ctx, g, impTeamName)
	if err != nil {
		return teamID, teamName, impTeamName, tlfID, conflicts, err
	}
	if teamDisplayName != referenceImpName {
		return teamID, teamName, impTeamName, tlfID, conflicts, fmt.Errorf("implicit team name mismatch: %s != %s",
			teamDisplayName, referenceImpName)
	}
	if team.IsPublic() != impTeamName.IsPublic {
		return teamID, teamName, impTeamName, tlfID, conflicts, fmt.Errorf("implicit team public-ness mismatch: %v != %v", team.IsPublic(), impTeamName.IsPublic)
	}

	tlfID = team.KBFSTLFID()

	return teamID, team.Name(), impTeamName, tlfID, conflicts, nil
}

// Lookup or create an implicit team by name like "alice,bob+bob@twitter (conflicted copy 2017-03-04 #1)"
// Resolves social assertions.
func LookupOrCreateImplicitTeam(ctx context.Context, g *libkb.GlobalContext, displayName string, public bool) (res keybase1.TeamID, teamName keybase1.TeamName, impTeamName keybase1.ImplicitTeamDisplayName, tlfID keybase1.TLFID, err error) {
	defer g.CTraceTimed(ctx, fmt.Sprintf("LookupOrCreateImplicitTeam(%v)", displayName),
		func() error { return err })()
	lookupName, err := ResolveImplicitTeamDisplayName(ctx, g, displayName, public)
	if err != nil {
		return res, teamName, impTeamName, tlfID, err
	}

	res, teamName, impTeamName, tlfID, _, err = lookupImplicitTeamAndConflicts(ctx, g, displayName, lookupName)
	if err != nil {
		if _, ok := err.(TeamDoesNotExistError); ok {
			if lookupName.ConflictInfo != nil {
				// Don't create it if a conflict is specified.
				// Unlikely a caller would know the conflict info if it didn't exist.
				return res, teamName, impTeamName, tlfID, err
			}
			// If the team does not exist, then let's create it
			impTeamName = lookupName
			res, teamName, err = CreateImplicitTeam(ctx, g, impTeamName)
			return res, teamName, impTeamName, tlfID, err
		}
		return res, teamName, impTeamName, tlfID, err
	}
	return res, teamName, impTeamName, tlfID, nil
}

func FormatImplicitTeamDisplayName(ctx context.Context, g *libkb.GlobalContext, impTeamName keybase1.ImplicitTeamDisplayName) (string, error) {
	return formatImplicitTeamDisplayNameCommon(ctx, g, impTeamName, nil)
}

// Format an implicit display name, but order the specified username first in each of the writer and reader lists if it appears.
func FormatImplicitTeamDisplayNameWithUserFront(ctx context.Context, g *libkb.GlobalContext, impTeamName keybase1.ImplicitTeamDisplayName, frontName libkb.NormalizedUsername) (string, error) {
	return formatImplicitTeamDisplayNameCommon(ctx, g, impTeamName, &frontName)
}

func formatImplicitTeamDisplayNameCommon(ctx context.Context, g *libkb.GlobalContext, impTeamName keybase1.ImplicitTeamDisplayName, optionalFrontName *libkb.NormalizedUsername) (string, error) {
	var writerNames []string
	for _, u := range impTeamName.Writers.KeybaseUsers {
		writerNames = append(writerNames, u)
	}
	for _, u := range impTeamName.Writers.UnresolvedUsers {
		writerNames = append(writerNames, u.String())
	}
	if optionalFrontName == nil {
		sort.Strings(writerNames)
	} else {
		sortStringsFront(writerNames, optionalFrontName.String())
	}

	var readerNames []string
	for _, u := range impTeamName.Readers.KeybaseUsers {
		readerNames = append(readerNames, u)
	}
	for _, u := range impTeamName.Readers.UnresolvedUsers {
		readerNames = append(readerNames, u.String())
	}
	if optionalFrontName == nil {
		sort.Strings(readerNames)
	} else {
		sortStringsFront(readerNames, optionalFrontName.String())
	}

	var suffix string
	if impTeamName.ConflictInfo != nil && impTeamName.ConflictInfo.IsConflict() {
		suffix = libkb.FormatImplicitTeamDisplayNameSuffix(*impTeamName.ConflictInfo)
	}

	if len(writerNames) == 0 {
		return "", fmt.Errorf("invalid implicit team name: no writers")
	}

	return kbfs.NormalizeNamesInTLF(writerNames, readerNames, suffix)
}

// Sort a list of strings but order `front` in front IF it appears.
func sortStringsFront(ss []string, front string) {
	sort.Slice(ss, func(i, j int) bool {
		a := ss[i]
		b := ss[j]
		if a == front {
			return true
		}
		if b == front {
			return false
		}
		return a < b
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package servenv

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"syscall"
	"time"

	"vitess.io/vitess/go/event"
	"vitess.io/vitess/go/vt/log"
)

var (
	onCloseHooks event.Hooks
	// ExitChan waits for a signal that tells the process to terminate
	ExitChan chan os.Signal
)

// Run starts listening for RPC and HTTP requests,
// and blocks until it the process gets a signal.
func Run(port int) {
	populateListeningURL(int32(port))
	createGRPCServer()
	onRunHooks.Fire()
	serveGRPC()
	serveSocketFile()

	l, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
	if err != nil {
		log.Exit(err)
	}
	go http.Serve(l, nil)

	ExitChan := make(chan os.Signal, 1)
	signal.Notify(ExitChan, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGINT)
	// Wait for signal
	<-ExitChan
	l.Close()

	startTime := time.Now()
	log.Infof("Entering lameduck mode for at least %v", *lameduckPeriod)
	log.Infof("Firing asynchronous OnTerm hooks")
	go onTermHooks.Fire()

	fireOnTermSyncHooks(*onTermTimeout)
	if remain := *lameduckPeriod - time.Since(startTime); remain > 0 {
		log.Infof("Sleeping an extra %v after OnTermSync to finish lameduck period", remain)
		time.Sleep(remain)
	}

	log.Info("Shutting down gracefully")
	Close()
}

// Close runs any registered exit hooks in parallel.
func Close() {
	onCloseHooks.Fire()
	ListeningURL = url.URL{}
}

// OnClose registers f to be run at the end of the app lifecycle.
// This happens after the lameduck period just before the program exits.
// All hooks are run in parallel.
func OnClose(f func()) {
	onCloseHooks.Add(f)
}
<commit_msg>only handle SIGTERM and SIGINT, drop SIGUSR1 handling<commit_after>/*
Copyright 2019 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package servenv

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"syscall"
	"time"

	"vitess.io/vitess/go/event"
	"vitess.io/vitess/go/vt/log"
)

var (
	onCloseHooks event.Hooks
	// ExitChan waits for a signal that tells the process to terminate
	ExitChan chan os.Signal
)

// Run starts listening for RPC and HTTP requests,
// and blocks until it the process gets a signal.
func Run(port int) {
	populateListeningURL(int32(port))
	createGRPCServer()
	onRunHooks.Fire()
	serveGRPC()
	serveSocketFile()

	l, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
	if err != nil {
		log.Exit(err)
	}
	go http.Serve(l, nil)

	ExitChan := make(chan os.Signal, 1)
	signal.Notify(ExitChan, syscall.SIGTERM, syscall.SIGINT)
	// Wait for signal
	<-ExitChan
	l.Close()

	startTime := time.Now()
	log.Infof("Entering lameduck mode for at least %v", *lameduckPeriod)
	log.Infof("Firing asynchronous OnTerm hooks")
	go onTermHooks.Fire()

	fireOnTermSyncHooks(*onTermTimeout)
	if remain := *lameduckPeriod - time.Since(startTime); remain > 0 {
		log.Infof("Sleeping an extra %v after OnTermSync to finish lameduck period", remain)
		time.Sleep(remain)
	}

	log.Info("Shutting down gracefully")
	Close()
}

// Close runs any registered exit hooks in parallel.
func Close() {
	onCloseHooks.Fire()
	ListeningURL = url.URL{}
}

// OnClose registers f to be run at the end of the app lifecycle.
// This happens after the lameduck period just before the program exits.
// All hooks are run in parallel.
func OnClose(f func()) {
	onCloseHooks.Add(f)
}
<|endoftext|>