Example #1
0
// request2Response converts will interpret VBucket field as Status field and
// re-interpret the request packate as response packet. This is required for
// UPR streams which are full duplex.
func request2Response(req *mcd.MCRequest) *mcd.MCResponse {
	return &mcd.MCResponse{
		Opcode: req.Opcode,
		Cas:    req.Cas,
		Opaque: req.Opaque,
		Status: mcd.Status(req.VBucket),
		Extras: req.Extras,
		Key:    req.Key,
		Body:   req.Body,
	}
}
Example #2
0
// Connect once to the server and work the UPR stream.  If anything
// goes wrong, return our level of progress in order to let our caller
// control any potential retries.
func (d *bucketDataSource) worker(server string, workerCh chan []uint16) int {
	atomic.AddUint64(&d.stats.TotWorkerBody, 1)

	if !d.isRunning() {
		return -1
	}

	atomic.AddUint64(&d.stats.TotWorkerConnect, 1)
	connect := d.options.Connect
	if connect == nil {
		connect = memcached.Connect
	}

	client, err := connect("tcp", server)
	if err != nil {
		atomic.AddUint64(&d.stats.TotWorkerConnectErr, 1)
		d.receiver.OnError(fmt.Errorf("worker connect, server: %s, err: %v",
			server, err))
		return 0
	}
	defer client.Close()
	atomic.AddUint64(&d.stats.TotWorkerConnectOk, 1)

	if d.auth != nil {
		var user, pswd string
		var adminCred bool
		if auth, ok := d.auth.(couchbase.AuthWithSaslHandler); ok {
			user, pswd = auth.GetSaslCredentials()
			adminCred = true
		} else {
			user, pswd, _ = d.auth.GetCredentials()
		}
		if user != "" {
			atomic.AddUint64(&d.stats.TotWorkerAuth, 1)
			res, err := client.Auth(user, pswd)
			if err != nil {
				atomic.AddUint64(&d.stats.TotWorkerAuthErr, 1)
				d.receiver.OnError(fmt.Errorf("worker auth, server: %s, user: %s, err: %v",
					server, user, err))
				return 0
			}
			if res.Status != gomemcached.SUCCESS {
				atomic.AddUint64(&d.stats.TotWorkerAuthFail, 1)
				d.receiver.OnError(&AuthFailError{ServerURL: server, User: user})
				return 0
			}
			if adminCred {
				atomic.AddUint64(&d.stats.TotWorkerAuthOk, 1)
				_, err = client.SelectBucket(d.bucketName)
				if err != nil {
					atomic.AddUint64(&d.stats.TotWorkerSelBktFail, 1)
					d.receiver.OnError(fmt.Errorf("worker select bucket err: %v", err))
					return 0
				}
				atomic.AddUint64(&d.stats.TotWorkerSelBktOk, 1)
			}
		}
	}

	uprOpenName := d.options.Name
	if uprOpenName == "" {
		uprOpenName = fmt.Sprintf("cbdatasource-%x", rand.Int63())
	}

	err = UPROpen(client, uprOpenName, d.options.FeedBufferSizeBytes)
	if err != nil {
		atomic.AddUint64(&d.stats.TotWorkerUPROpenErr, 1)
		d.receiver.OnError(err)
		return 0
	}
	atomic.AddUint64(&d.stats.TotWorkerUPROpenOk, 1)

	ackBytes :=
		uint32(d.options.FeedBufferAckThreshold * float32(d.options.FeedBufferSizeBytes))

	sendCh := make(chan *gomemcached.MCRequest, 1)
	sendEndCh := make(chan struct{})
	recvEndCh := make(chan struct{})

	cleanup := func(progress int, err error) int {
		if err != nil {
			d.receiver.OnError(err)
		}
		go func() {
			<-recvEndCh
			close(sendCh)
		}()
		return progress
	}

	currVBuckets := make(map[uint16]*VBucketState)
	currVBucketsMutex := sync.Mutex{} // Protects currVBuckets.

	go func() { // Sender goroutine.
		defer close(sendEndCh)

		atomic.AddUint64(&d.stats.TotWorkerTransmitStart, 1)
		for msg := range sendCh {
			atomic.AddUint64(&d.stats.TotWorkerTransmit, 1)
			err := client.Transmit(msg)
			if err != nil {
				atomic.AddUint64(&d.stats.TotWorkerTransmitErr, 1)
				d.receiver.OnError(fmt.Errorf("client.Transmit, err: %v", err))
				return
			}
			atomic.AddUint64(&d.stats.TotWorkerTransmitOk, 1)
		}
		atomic.AddUint64(&d.stats.TotWorkerTransmitDone, 1)
	}()

	go func() { // Receiver goroutine.
		defer close(recvEndCh)

		atomic.AddUint64(&d.stats.TotWorkerReceiveStart, 1)

		var hdr [gomemcached.HDR_LEN]byte
		var pkt gomemcached.MCRequest
		var res gomemcached.MCResponse

		// Track received bytes in case we need to buffer-ack.
		recvBytesTotal := uint32(0)

		conn := client.Hijack()

		for {
			// TODO: memory allocation here.
			atomic.AddUint64(&d.stats.TotWorkerReceive, 1)
			_, err := pkt.Receive(conn, hdr[:])
			if err != nil {
				atomic.AddUint64(&d.stats.TotWorkerReceiveErr, 1)
				d.receiver.OnError(fmt.Errorf("pkt.Receive, err: %v", err))
				return
			}
			atomic.AddUint64(&d.stats.TotWorkerReceiveOk, 1)

			if pkt.Opcode == gomemcached.UPR_MUTATION ||
				pkt.Opcode == gomemcached.UPR_DELETION ||
				pkt.Opcode == gomemcached.UPR_EXPIRATION {
				atomic.AddUint64(&d.stats.TotUPRDataChange, 1)

				vbucketID := pkt.VBucket

				currVBucketsMutex.Lock()

				vbucketState := currVBuckets[vbucketID]
				if vbucketState == nil || vbucketState.State != "running" {
					currVBucketsMutex.Unlock()
					atomic.AddUint64(&d.stats.TotUPRDataChangeStateErr, 1)
					d.receiver.OnError(fmt.Errorf("error: DataChange,"+
						" wrong vbucketState: %#v, err: %v", vbucketState, err))
					return
				}

				if !vbucketState.SnapSaved {
					// NOTE: Following the ep-engine's approach, we
					// wait to persist SnapStart/SnapEnd until we see
					// the first mutation/deletion in the new snapshot
					// range.  That reduces a race window where if we
					// kill and restart this process right now after a
					// setVBucketMetaData() and before the next,
					// first-mutation-in-snapshot, then a restarted
					// stream-req using this just-saved
					// SnapStart/SnapEnd might have a lastSeq number <
					// SnapStart, where Couchbase Server will respond
					// to the stream-req with an ERANGE error code.
					v, _, err := d.getVBucketMetaData(vbucketID)
					if err != nil || v == nil {
						currVBucketsMutex.Unlock()
						d.receiver.OnError(fmt.Errorf("error: DataChange,"+
							" getVBucketMetaData, vbucketID: %d, err: %v",
							vbucketID, err))
						return
					}

					v.SnapStart = vbucketState.SnapStart
					v.SnapEnd = vbucketState.SnapEnd

					err = d.setVBucketMetaData(vbucketID, v)
					if err != nil {
						currVBucketsMutex.Unlock()
						d.receiver.OnError(fmt.Errorf("error: DataChange,"+
							" getVBucketMetaData, vbucketID: %d, err: %v",
							vbucketID, err))
						return
					}

					vbucketState.SnapSaved = true
				}

				currVBucketsMutex.Unlock()

				seq := binary.BigEndian.Uint64(pkt.Extras[:8])

				if pkt.Opcode == gomemcached.UPR_MUTATION {
					atomic.AddUint64(&d.stats.TotUPRDataChangeMutation, 1)
					err = d.receiver.DataUpdate(vbucketID, pkt.Key, seq, &pkt)
				} else {
					if pkt.Opcode == gomemcached.UPR_DELETION {
						atomic.AddUint64(&d.stats.TotUPRDataChangeDeletion, 1)
					} else {
						atomic.AddUint64(&d.stats.TotUPRDataChangeExpiration, 1)
					}
					err = d.receiver.DataDelete(vbucketID, pkt.Key, seq, &pkt)
				}

				if err != nil {
					atomic.AddUint64(&d.stats.TotUPRDataChangeErr, 1)
					d.receiver.OnError(fmt.Errorf("error: DataChange, err: %v", err))
					return
				}

				atomic.AddUint64(&d.stats.TotUPRDataChangeOk, 1)
			} else {
				res.Opcode = pkt.Opcode
				res.Opaque = pkt.Opaque
				res.Status = gomemcached.Status(pkt.VBucket)
				res.Extras = pkt.Extras
				res.Cas = pkt.Cas
				res.Key = pkt.Key
				res.Body = pkt.Body

				atomic.AddUint64(&d.stats.TotWorkerHandleRecv, 1)
				currVBucketsMutex.Lock()
				err := d.handleRecv(sendCh, currVBuckets, &res)
				currVBucketsMutex.Unlock()
				if err != nil {
					atomic.AddUint64(&d.stats.TotWorkerHandleRecvErr, 1)
					d.receiver.OnError(fmt.Errorf("error: HandleRecv, err: %v", err))
					return
				}
				atomic.AddUint64(&d.stats.TotWorkerHandleRecvOk, 1)
			}

			recvBytesTotal +=
				uint32(gomemcached.HDR_LEN) +
					uint32(len(pkt.Key)+len(pkt.Extras)+len(pkt.Body))
			if ackBytes > 0 && recvBytesTotal > ackBytes {
				atomic.AddUint64(&d.stats.TotUPRBufferAck, 1)
				ack := &gomemcached.MCRequest{Opcode: gomemcached.UPR_BUFFERACK}
				ack.Extras = make([]byte, 4) // TODO: Memory mgmt.
				binary.BigEndian.PutUint32(ack.Extras, uint32(recvBytesTotal))
				sendCh <- ack
				recvBytesTotal = 0
			}
		}
	}()

	atomic.AddUint64(&d.stats.TotWorkerBodyKick, 1)
	d.Kick("new-worker")

	for {
		select {
		case <-sendEndCh:
			atomic.AddUint64(&d.stats.TotWorkerSendEndCh, 1)
			return cleanup(0, nil)

		case <-recvEndCh:
			// If we lost a connection, then maybe a node was rebalanced out,
			// or failed over, so ask for a cluster refresh just in case.
			d.Kick("recvEndCh")

			atomic.AddUint64(&d.stats.TotWorkerRecvEndCh, 1)
			return cleanup(0, nil)

		case wantVBucketIDs, alive := <-workerCh:
			atomic.AddUint64(&d.stats.TotRefreshWorker, 1)

			if !alive {
				atomic.AddUint64(&d.stats.TotRefreshWorkerDone, 1)
				return cleanup(-1, nil) // We've been asked to shutdown.
			}

			currVBucketsMutex.Lock()
			err := d.refreshWorker(sendCh, currVBuckets, wantVBucketIDs)
			currVBucketsMutex.Unlock()
			if err != nil {
				return cleanup(0, err)
			}

			atomic.AddUint64(&d.stats.TotRefreshWorkerOk, 1)
		}
	}

	return cleanup(-1, nil) // Unreached.
}
Example #3
0
// constants used for memcached protocol
const (
	uprOPEN        = mcd.CommandCode(0x50) // Open a upr connection with `name`
	uprAddSTREAM   = mcd.CommandCode(0x51) // Sent by ebucketmigrator to upr consumer
	uprCloseSTREAM = mcd.CommandCode(0x52) // Sent by ebucketmigrator to upr consumer
	uprFailoverLOG = mcd.CommandCode(0x54) // Request all known failover ids for restart
	uprStreamREQ   = mcd.CommandCode(0x53) // Stream request from consumer to producer
	uprStreamEND   = mcd.CommandCode(0x55) // Sent by producer when it is going to end stream
	uprSnapshotM   = mcd.CommandCode(0x56) // Sent by producer for a new snapshot
	uprMUTATION    = mcd.CommandCode(0x57) // Notifies SET/ADD/REPLACE/etc.  on the server
	uprDELETION    = mcd.CommandCode(0x58) // Notifies DELETE on the server
	uprEXPIRATION  = mcd.CommandCode(0x59) // Notifies key expiration
	uprFLUSH       = mcd.CommandCode(0x5a) // Notifies vbucket flush
)
const (
	rollBack = mcd.Status(0x23)
)

// FailoverLog is a slice of 2 element array, containing a list of,
// [[vuuid, sequence-no], [vuuid, sequence-no] ...]
type FailoverLog [][2]uint64

// UprStream will maintain stream information per vbucket
type UprStream struct {
	Vbucket  uint16 // vbucket id
	Vuuid    uint64 // vbucket uuid
	Opaque   uint32 // messages from producer to this stream have same value
	Highseq  uint64 // to be supplied by the application
	Startseq uint64 // to be supplied by the application
	Endseq   uint64 // to be supplied by the application
	Flog     FailoverLog
func (feed *UprFeed) runFeed(ch chan *UprEvent) {
	defer close(ch)
	var headerBuf [gomemcached.HDR_LEN]byte
	var pkt gomemcached.MCRequest
	var event *UprEvent

	mc := feed.conn.Hijack()
	uprStats := &feed.stats

loop:
	for {
		sendAck := false
		bytes, err := pkt.Receive(mc, headerBuf[:])
		if err != nil {
			ul.LogError("", "", "Error in receive %s", err.Error())
			feed.Error = err
			// send all the stream close messages to the client
			feed.doStreamClose(ch)
			break loop
		} else {
			event = nil
			res := &gomemcached.MCResponse{
				Opcode: pkt.Opcode,
				Cas:    pkt.Cas,
				Opaque: pkt.Opaque,
				Status: gomemcached.Status(pkt.VBucket),
				Extras: pkt.Extras,
				Key:    pkt.Key,
				Body:   pkt.Body,
			}

			vb := vbOpaque(pkt.Opaque)
			uprStats.TotalBytes = uint64(bytes)

			feed.mu.RLock()
			stream := feed.vbstreams[vb]
			feed.mu.RUnlock()

			switch pkt.Opcode {
			case gomemcached.UPR_STREAMREQ:
				if stream == nil {
					ul.LogError("", "", "Stream not found for vb %d: %#v", vb, pkt)
					break loop
				}
				status, rb, flog, err := handleStreamRequest(res)
				if status == gomemcached.ROLLBACK {
					event = makeUprEvent(pkt, stream)
					// rollback stream
					msg := "UPR_STREAMREQ with rollback %d for vb %d Failed: %v"
					ul.LogError("", "", msg, rb, vb, err)
					// delete the stream from the vbmap for the feed
					feed.mu.Lock()
					delete(feed.vbstreams, vb)
					feed.mu.Unlock()

				} else if status == gomemcached.SUCCESS {
					event = makeUprEvent(pkt, stream)
					event.Seqno = stream.StartSeq
					event.FailoverLog = flog
					stream.connected = true
					ul.LogInfo("", "", "UPR_STREAMREQ for vb %d successful", vb)

				} else if err != nil {
					msg := "UPR_STREAMREQ for vbucket %d erro %s"
					ul.LogError("", "", msg, vb, err.Error())
					event = &UprEvent{
						Opcode:  gomemcached.UPR_STREAMREQ,
						Status:  status,
						VBucket: vb,
						Error:   err,
					}
				}

			case gomemcached.UPR_MUTATION,
				gomemcached.UPR_DELETION,
				gomemcached.UPR_EXPIRATION:
				if stream == nil {
					ul.LogError("", "", "Stream not found for vb %d: %#v", vb, pkt)
					break loop
				}
				event = makeUprEvent(pkt, stream)
				uprStats.TotalMutation++
				sendAck = true

			case gomemcached.UPR_STREAMEND:
				if stream == nil {
					ul.LogError("", "", "Stream not found for vb %d: %#v", vb, pkt)
					break loop
				}
				//stream has ended
				event = makeUprEvent(pkt, stream)
				ul.LogInfo("", "", "Stream Ended for vb %d", vb)
				sendAck = true

				feed.mu.Lock()
				delete(feed.vbstreams, vb)
				feed.mu.Unlock()

			case gomemcached.UPR_SNAPSHOT:
				if stream == nil {
					ul.LogError("", "", "Stream not found for vb %d: %#v", vb, pkt)
					break loop
				}
				// snapshot marker
				event = makeUprEvent(pkt, stream)
				event.SnapstartSeq = binary.BigEndian.Uint64(pkt.Extras[0:8])
				event.SnapendSeq = binary.BigEndian.Uint64(pkt.Extras[8:16])
				event.SnapshotType = binary.BigEndian.Uint32(pkt.Extras[16:20])
				uprStats.TotalSnapShot++
				sendAck = true

			case gomemcached.UPR_FLUSH:
				if stream == nil {
					ul.LogError("", "", "Stream not found for vb %d: %#v", vb, pkt)
					break loop
				}
				// special processing for flush ?
				event = makeUprEvent(pkt, stream)

			case gomemcached.UPR_CLOSESTREAM:
				if stream == nil {
					ul.LogError("", "", "Stream not found for vb %d: %#v", vb, pkt)
					break loop
				}
				event = makeUprEvent(pkt, stream)
				event.Opcode = gomemcached.UPR_STREAMEND // opcode re-write !!
				msg := "Stream Closed for vb %d StreamEnd simulated"
				ul.LogInfo("", "", msg, vb)
				sendAck = true

				feed.mu.Lock()
				delete(feed.vbstreams, vb)
				feed.mu.Unlock()

			case gomemcached.UPR_ADDSTREAM:
				ul.LogWarn("", "", "Opcode %v not implemented", pkt.Opcode)

			case gomemcached.UPR_CONTROL, gomemcached.UPR_BUFFERACK:
				if res.Status != gomemcached.SUCCESS {
					msg := "Opcode %v received status %d"
					ul.LogWarn("", "", msg, pkt.Opcode.String(), res.Status)
				}

			case gomemcached.UPR_NOOP:
				// send a NOOP back
				noop := &gomemcached.MCRequest{
					Opcode: gomemcached.UPR_NOOP,
				}
				feed.transmitCh <- noop

			default:
				msg := "Recived an unknown response for vbucket %d"
				ul.LogError("", "", msg, vb)
			}
		}

		if event != nil {
			select {
			case ch <- event:
			case <-feed.closer:
				break loop
			}

			feed.mu.RLock()
			l := len(feed.vbstreams)
			feed.mu.RUnlock()

			if event.Opcode == gomemcached.UPR_CLOSESTREAM && l == 0 {
				ul.LogInfo("", "", "No more streams")
				break loop
			}

		}

		needToSend, sendSize := feed.SendBufferAck(sendAck, uint32(bytes))
		if needToSend {
			bufferAck := &gomemcached.MCRequest{
				Opcode: gomemcached.UPR_BUFFERACK,
			}
			bufferAck.Extras = make([]byte, 4)
			binary.BigEndian.PutUint32(bufferAck.Extras[:4], uint32(sendSize))
			feed.transmitCh <- bufferAck
			uprStats.TotalBufferAckSent++
		}
	}

	feed.transmitCl <- true
}