예제 #1
0
파일: broker.go 프로젝트: jimjh/octopi
// main starts a broker instance.
// Configuration Options:
//  - port number
//  - host:port of register/leader
func main() {

	log.SetVerbose(log.DEBUG)

	defer func() {
		if r := recover(); nil != r {
			log.Error("%v", r)
		}
	}()

	// parse command line args
	var configFile = flag.String("conf", "conf.json", "configuration file")
	flag.Parse()

	log.Info("Initializing broker with options from %s.", *configFile)

	// init configuration
	config, err := config.Init(*configFile)
	checkError(err)

	log.Info("Options read were: %v", config)

	port, err := strconv.Atoi(config.Get("port", PORT))
	checkError(err)

	log.SetPrefix(fmt.Sprintf("broker@%d: ", port))

	broker, err = brokerimpl.New(config)
	checkError(err)

	listenHttp(port)

}
예제 #2
0
파일: brokerimpl.go 프로젝트: jimjh/octopi
// register sends a follow request to the given leader.
func (b *Broker) register() error {

	log.Info("In register()")
	follow := &protocol.FollowRequest{b.tails(), protocol.HostPort(b.Origin())}
	payload, err := b.leader.Send(follow, math.MaxInt32, b.Origin())

	if nil != err {
		return err
	}

	log.Info("Registered with leader.")

	var ack protocol.FollowACK
	if err := json.Unmarshal(payload, &ack); nil != err {
		return err
	}

	for topic, checkpoint := range ack.Truncate {
		if log, exists := b.logs[topic]; exists {
			log.Close()
			delete(b.logs, topic)
		}
		if err := truncateLog(b.config, topic, checkpoint); nil != err {
			return err
		}
	}

	// successful connection
	go b.failSafeCatchUp()

	log.Info("Catching up with leader.")
	return nil

}
예제 #3
0
파일: regimpl.go 프로젝트: jimjh/octopi
// CheckNewLeader allows re-sending of disconnect requests
// every interval until a leader is connected
func (r *Register) CheckNewLeader() {
	select {
	case <-r.singleton:
		for r.leader == EMPTY {
			r.LeaderDisconnect()
			time.Sleep(LEADERWAIT * time.Millisecond)
			log.Info("CheckNewLeader leader is ", r.leader)
		}
		r.singleton <- 1
	default:
		// return if an instance already running
	}
	log.Info("Returning from CheckNewLeader")
}
예제 #4
0
파일: log.go 프로젝트: jimjh/octopi
// Writes the given entry at the end of the broker log.
func (log *Log) WriteNext(entry *LogEntry) error {

	// in case of error, revert
	checkpoint, _ := log.Seek(0, os.SEEK_CUR)
	bail := func() { log.Seek(checkpoint, os.SEEK_SET) }

	if string(entry.RequestId) == string(log.lastWritten) {
		return nil
	}

	if err := log.writeLength(entry); nil != err {
		bail()
		return err
	}

	if _, err := log.writeEntry(entry); nil != err {
		bail()
		return err
	}

	log.lastWritten = entry.RequestId
	debug.Info("wrote request %v.", entry.RequestId)

	return nil

}
예제 #5
0
파일: brokerimpl.go 프로젝트: jimjh/octopi
// initLogs initializes the logs map.
func (b *Broker) initLogs() {

	pattern := filepath.Join(b.config.LogDir(), "*"+EXT)
	matches, err := filepath.Glob(pattern)
	if nil != err {
		log.Panic("Unable to read from log directory: %s", b.config.LogDir())
	}

	for _, name := range matches {

		topic := filepath.Base(name)
		topic = topic[0 : len(topic)-len(EXT)]

		file, err := OpenLog(b.config, topic, -1)

		if nil != err {
			log.Error("Ignoring bad log file: %s", name)
			continue
		}

		b.logs[topic] = file
		log.Info("Found log file for %s.", topic)

	}

}
예제 #6
0
파일: sync.go 프로젝트: jimjh/octopi
// ackFollower sends an acknowledgement to the follower.
func (b *Broker) ackFollower(f *Follower) error {

	b.lock.Lock()
	defer b.lock.Unlock()

	ack := new(protocol.Ack)
	if b.role != LEADER || f.conn.RemoteAddr().String() == b.Origin() {
		log.Warn("Denying follow requests from %s.", f.conn.RemoteAddr())
		ack.Status = protocol.StatusFailure
	} else {

		inner := new(protocol.FollowACK)
		inner.Truncate = make(Offsets)
		for topic, checkpoint := range b.checkpoints {
			log.Info("Checkpoint is %v", checkpoint)
			if f.tails[topic] > checkpoint {
				inner.Truncate[topic] = checkpoint
				f.tails[topic] = checkpoint
			}
		}

		ack.Status = protocol.StatusSuccess
		ack.Payload, _ = json.Marshal(inner)

	}

	return websocket.JSON.Send(f.conn, ack)

}
예제 #7
0
파일: sync.go 프로젝트: jimjh/octopi
// SyncFollower streams updates to a follower through the given connection.
// Once the follower has fully caught up, add it to the follower set.
func (b *Broker) SyncFollower(conn *websocket.Conn, tails Offsets, hostport protocol.HostPort) error {

	follower := &Follower{
		conn:     conn,
		tails:    tails,
		hostport: hostport,
		quit:     make(chan interface{}, 1),
	}

	if err := b.ackFollower(follower); nil != err {
		return err
	}

	log.Debug("Begin synchronizing follower %v.", follower.hostport)
	for !follower.caughtUp(b) {
		if err := follower.catchUp(b); nil != err {
			return err
		}
	}

	log.Info("Follower %v has fully caught up.", follower.hostport)

	<-follower.quit
	return nil

}
예제 #8
0
파일: publish.go 프로젝트: jimjh/octopi
// producer handles incoming produce requests. Producers may send multiple
// produce requests on the same persistent connection. The function exits when
// an `io.EOF` is received on the connection.
func producer(conn *websocket.Conn) {

	defer conn.Close()

	for {

		var request protocol.ProduceRequest

		err := websocket.JSON.Receive(conn, &request)
		if err == io.EOF { // graceful shutdown
			break
		}

		if nil != err {
			log.Warn("Ignoring invalid message from %v.", conn.RemoteAddr())
			continue
		}

		ack := new(protocol.Ack)
		if err := broker.Publish(request.Topic, request.ID, &request.Message); nil != err {
			log.Error(err.Error())
			ack.Status = protocol.StatusFailure
		} else {
			ack.Status = protocol.StatusSuccess
		}
		// TODO: should redirect if this node is not the leader

		websocket.JSON.Send(conn, &ack)

	}

	log.Info("Closed producer connection from %v.", conn.RemoteAddr())

}
예제 #9
0
파일: regimpl.go 프로젝트: jimjh/octopi
func (r *Register) SetLeader(hostport string) {
	r.lock.Lock()
	defer r.lock.Unlock()
	r.leader = hostport
	r.seenBrokers[hostport] = true
	log.Info("SetLeader setting leader to be %v", r.leader)
}
예제 #10
0
파일: broker.go 프로젝트: jimjh/octopi
// listenHttp starts a http server at the given port and listens for incoming
// websocket messages.
func listenHttp(port int) {
	http.Handle("/"+protocol.PUBLISH, websocket.Handler(producer))
	http.Handle("/"+protocol.FOLLOW, websocket.Handler(follower))
	http.Handle("/"+protocol.SUBSCRIBE, websocket.Handler(consumer))
	http.Handle("/"+protocol.SWAP, websocket.Handler(register))
	log.Info("HTTP server started on %d.", port)
	http.ListenAndServe(":"+strconv.Itoa(port), nil)
}
예제 #11
0
파일: brokerimpl.go 프로젝트: jimjh/octopi
// ChangeLeader closes the current leader connection and re-registers.
func (b *Broker) ChangeLeader() error {

	b.leader.Reset(b.config.Register())
	log.Info("Reset to be %v", b.config.Register())

	b.lock.Lock()
	defer b.lock.Unlock()

	return b.register()

}
예제 #12
0
파일: log.go 프로젝트: jimjh/octopi
// readEntry reads the next n bytes and decodes them into a log entry.
func (log *Log) readEntry(n uint32) (*LogEntry, error) {

	debug.Info("Making slice of size n=%v", n)
	buf := make([]byte, n)
	total := uint32(0)

	for { // read exactly n bytes, or until error
		read, err := log.Read(buf[total:])
		if nil != err {
			return nil, err
		}
		total += uint32(read)
		if total >= n {
			break
		}
	}

	var entry LogEntry
	return &entry, entry.decode(buf)

}
예제 #13
0
파일: regimpl.go 프로젝트: jimjh/octopi
// notifyFollowers notifies the followers of a change in leader
func (r *Register) notifyFollower(follower string, is map[string]bool) {
	conn, err := websocket.Dial("ws://"+follower+"/"+protocol.SWAP, "", "http://"+follower+"/")

	log.Info("Notifying %v", follower)

	// failed to contact the follower
	if nil != err {
		// remove from follower set
		r.RemoveFollower(follower)
		return
	}

	err = websocket.JSON.Send(conn, is)

	// failed to send to the follower
	if nil != err {
		// remove from follower set
		r.RemoveFollower(follower)
		return
	}
}
예제 #14
0
파일: pub_sub.go 프로젝트: jimjh/octopi
// removeFollower disconnects follower from followers set.
func (b *Broker) removeFollower(follower *Follower) {

	_, exists := b.followers[follower]
	if !exists {
		return
	}

	delete(b.followers, follower)

	// create struct to communicate with register
	var removeFollow protocol.InsyncChange
	removeFollow.Type = protocol.REMOVE
	removeFollow.HostPort = follower.hostport

	// add in-sync follower
	// check if disconnect from register. if so, exit.
	websocket.JSON.Send(b.regConn, removeFollow)
	// checkError(err) // FIXME: exiting is not the correct thing to do

	follower.quit <- nil

	log.Info("Removed follower %v from follower set.", follower.hostport)

}
예제 #15
0
파일: pub_sub.go 프로젝트: jimjh/octopi
// replicate  replicates the given log entry across all followers.
func (b *Broker) replicate(topic string, entry *LogEntry) error {

	// send message to all followers
	for follower, _ := range b.followers {
		sync := &protocol.Sync{topic, entry.Message, entry.RequestId}
		log.Info("Sending %v to %v", entry.Message.ID, follower.hostport)
		if err := websocket.JSON.Send(follower.conn, sync); nil != err {
			// lost
			b.removeFollower(follower)
		}
	}

	// wait for ACK
	for follower, _ := range b.followers {
		var ack protocol.SyncACK
		if err := websocket.JSON.Receive(follower.conn, &ack); nil != err {
			// lost
			b.removeFollower(follower)
		}
	}

	return nil

}