Example #1
0
// main entry point for data writer
func main() {
	var err error
	var writerSocket *zmq4.Socket
	var eventSubSocket *zmq4.Socket

	fog.Info("program starts")

	if writerSocket, err = createWriterSocket(); err != nil {
		fog.Critical("createWriterSocket %s", err)
	}
	defer writerSocket.Close()

	fog.Info("binding writer socket to %s", dataWriterAddress)
	if err = writerSocket.Bind(dataWriterAddress); err != nil {
		fog.Critical("Bind(%s) %s", dataWriterAddress, err)
	}

	if eventSubSocket, err = createEventSubSocket(); err != nil {
		fog.Critical("createEventSubSocket %s", err)
	}
	defer eventSubSocket.Close()

	fog.Info("connecting event sub socket to %s", eventAggregatorPubAddress)
	if err = eventSubSocket.Connect(eventAggregatorPubAddress); err != nil {
		fog.Critical("Connect(%s) %s", eventAggregatorPubAddress, err)
	}

	messageChan := NewMessageHandler()

	reactor := zmq4.NewReactor()
	reactor.AddChannel(tools.NewSignalWatcher(), 1, tools.SigtermHandler)
	reactor.AddSocket(writerSocket, zmq4.POLLIN,
		NewWriterSocketHandler(writerSocket, messageChan))
	reactor.AddSocket(eventSubSocket, zmq4.POLLIN,
		NewEventSubSocketHandler(eventSubSocket))

	fog.Debug("starting reactor.Run")
	reactor.SetVerbose(true)
	err = reactor.Run(reactorPollingInterval)
	if err == tools.SigtermError {
		fog.Info("program terminates normally due to SIGTERM")
	} else if errno, ok := err.(syscall.Errno); ok {
		// we can get 'interrupted system call' if we get SIGTERM while
		// a socket is waiting on a read. That's not too bad.
		if errno == syscall.EINTR {
			fog.Warn("reactor.Run returns '%s' assuming SIGTERM", errno)
		} else {
			fog.Error("reactor.Run returns %T '%s'", errno, errno)
		}
	} else {
		fog.Error("reactor.Run returns %T %s", err, err)
	}
}
Example #2
0
func testPOST(serviceDomain string, useTLS bool) {
	var contentLength uint64 = 1024 * 1024 * 1024
	fog.Debug("start post %dmb", contentLength/(1024*1024))

	client, scheme := getClient(useTLS)

	url := fmt.Sprintf("%s://%s/admin", scheme, serviceDomain)
	bodyReader := tools.NewSizeReader(contentLength)
	request, err := http.NewRequest("POST", url, bodyReader)
	if err != nil {
		fog.Critical("NewRequest (POST) failed %s", err)
	}
	request.TransferEncoding = []string{"identity"}
	request.Header.Add("Content-Type", "text/plain")
	request.Header.Add("Content-Length", fmt.Sprintf("%s", contentLength))
	request.ContentLength = int64(contentLength)
	response, err := client.Do(request)
	if err != nil {
		fog.Error("post %s", err)
		return
	}
	tools.ReadAndDiscard(response.Body)
	response.Body.Close()

	fog.Debug("finished post %s", response.Status)
}
Example #3
0
// sendErrorReply sends an error reply to the client
func sendErrorReply(conn net.Conn, httpCode int, errorMessage string) {
	reply := fmt.Sprintf("HTTP/1.0 %d %s\r\n\r\n%s",
		httpCode, http.StatusText(httpCode), errorMessage)
	if _, err := conn.Write([]byte(reply)); err != nil {
		fog.Error("Write error: %s %s", reply, err)
	}
}
Example #4
0
func handleFinishConjoinedArchive(message types.Message,
	nimbusioWriter writer.NimbusioWriter,
	replyChan chan<- *reply.ReplyMessage) {

	var conjoined msg.Conjoined
	var err error

	conjoined, err = msg.UnmarshalConjoined(message.Marshalled)
	if err != nil {
		fog.Error("UnmarshalConjoined failed %s", err)
		return
	}

	replyMessage := reply.NewReplyMessage("finish-conjoined-archive", message.ID,
		conjoined.UserRequestID, conjoined.ReturnAddress)

	lgr := logger.NewLogger(conjoined.UserRequestID, conjoined.UnifiedID,
		0, 0, conjoined.Key)
	lgr.Info("finish-conjoined-archive (%d)", conjoined.CollectionID)

	if err = nimbusioWriter.FinishConjoinedArchive(conjoined); err != nil {
		lgr.Error("StartConjoinedArchive: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	replyMessage.Success()
	replyChan <- replyMessage
	return
}
Example #5
0
func handleDestroyKey(message types.Message,
	nimbusioWriter writer.NimbusioWriter,
	replyChan chan<- *reply.ReplyMessage) {

	var destroyKey msg.DestroyKey
	var err error

	destroyKey, err = msg.UnmarshalDestroyKey(message.Marshalled)
	if err != nil {
		fog.Error("UnmarshalDestroyKey failed %s", err)
		return
	}

	replyMessage := reply.NewReplyMessage("destroy-key", message.ID,
		destroyKey.UserRequestID, destroyKey.ReturnAddress)

	lgr := logger.NewLogger(destroyKey.UserRequestID, destroyKey.UnifiedID,
		destroyKey.ConjoinedPart, destroyKey.SegmentNum, destroyKey.Key)
	lgr.Info("archive-key-cancel")

	if err = nimbusioWriter.DestroyKey(destroyKey); err != nil {
		lgr.Error("DestroyKey: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	replyMessage.Success()
	replyChan <- replyMessage
	return
}
Example #6
0
func handleArchiveKeyCancel(message types.Message,
	nimbusioWriter writer.NimbusioWriter,
	replyChan chan<- *reply.ReplyMessage) {

	var archiveKeyCancel msg.ArchiveKeyCancel
	var err error

	archiveKeyCancel, err = msg.UnmarshalArchiveKeyCancel(message.Marshalled)
	if err != nil {
		fog.Error("UnmarshalArchiveKeyCancel failed %s", err)
		return
	}

	replyMessage := reply.NewReplyMessage("archive-key-cancel", message.ID,
		archiveKeyCancel.UserRequestID, archiveKeyCancel.ReturnAddress)

	lgr := logger.NewLogger(archiveKeyCancel.UserRequestID, archiveKeyCancel.UnifiedID,
		archiveKeyCancel.ConjoinedPart, archiveKeyCancel.SegmentNum, "")
	lgr.Info("archive-key-cancel")

	if err = nimbusioWriter.CancelSegment(archiveKeyCancel); err != nil {
		lgr.Error("CancelSegment: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	replyMessage.Success()
	replyChan <- replyMessage
	return
}
func handleSignoff(message types.Message) {
	returnAddress, err := msg.UnmarshalReturnAddress(message.Marshalled)
	if err != nil {
		fog.Error("handleSignoff: unable to unmarshall %s", err)
		return
	}
	fog.Info("signoff from %s", returnAddress.ClientAddress)
}
Example #8
0
func handleArchiveKeyNext(message types.Message,
	nimbusioWriter writer.NimbusioWriter,
	replyChan chan<- *reply.ReplyMessage) {

	var archiveKeyNext msg.ArchiveKeyNext
	var md5Digest []byte
	var err error

	archiveKeyNext, err = msg.UnmarshalArchiveKeyNext(message.Marshalled)
	if err != nil {
		fog.Error("UnmarshalArchiveKeyNext failed %s", err)
		return
	}

	replyMessage := reply.NewReplyMessage("archive-key-final", message.ID,
		archiveKeyNext.UserRequestID, archiveKeyNext.ReturnAddress)

	lgr := logger.NewLogger(archiveKeyNext.UserRequestID, archiveKeyNext.UnifiedID,
		archiveKeyNext.ConjoinedPart, archiveKeyNext.SegmentNum, archiveKeyNext.Key)
	lgr.Info("archive-key-Next")

	if archiveKeyNext.SegmentSize != uint64(len(message.Data)) {
		lgr.Error("size mismatch (%d != %d)", archiveKeyNext.SegmentSize,
			len(message.Data))
		replyMessage.Error("size-mismatch", "segment size does not match expected value")
		replyChan <- replyMessage
		return
	}

	md5Digest, err = base64.StdEncoding.DecodeString(archiveKeyNext.EncodedSegmentMD5Digest)
	if err != nil {
		lgr.Error("base64.StdEncoding.DecodeString %s", err)
		replyMessage.Error("base64", "base64.StdEncoding.DecodeString")
		replyChan <- replyMessage
		return
	}

	if !MD5DigestMatches(message.Data, md5Digest) {
		lgr.Error("md5 mismatch")
		replyMessage.Error("md5-mismatch", "segment md5 does not match expected value")
		replyChan <- replyMessage
		return
	}

	_, err = nimbusioWriter.StoreSequence(archiveKeyNext.UserRequestID,
		archiveKeyNext.Segment,
		archiveKeyNext.Sequence, message.Data)
	if err != nil {
		lgr.Error("StoreSequence: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	replyMessage.Success()
	replyChan <- replyMessage
	return
}
Example #9
0
func NewReplyHandler() chan<- *ReplyMessage {
	replyChan := make(chan *ReplyMessage, replyChanCapacity)
	pushSockets := make(map[string]*zmq4.Socket)

	go func() {
		for replyMessage := range replyChan {

			marshalledReply, err := json.Marshal(replyMessage.Content)
			if err != nil {
				fog.Error("unable to marshall reply %s %s", replyMessage, err)
				continue
			}

			var pushSocket *zmq4.Socket
			var ok bool
			pushSocket, ok = pushSockets[replyMessage.ClientAddress]
			if !ok {
				fog.Info("creating PUSH socket to %s", replyMessage.ClientAddress)
				if pushSocket, err = createPushSocket(); err != nil {
					fog.Error("Unable to create PUSH socket for %s %s",
						replyMessage.ClientAddress, err)
					continue
				}
				if err = pushSocket.Connect(replyMessage.ClientAddress); err != nil {
					fog.Error("Unable to Connect PUSH socket to %s %s",
						replyMessage.ClientAddress, err)
					pushSocket.Close()
					continue
				}
				pushSockets[replyMessage.ClientAddress] = pushSocket
			}

			if _, err = pushSocket.SendMessage(marshalledReply); err != nil {
				fog.Error("pushSocket SendMessage to %s failed %s",
					replyMessage.ClientAddress, err)
				pushSocket.Close()
				delete(pushSockets, replyMessage.ClientAddress)
				continue
			}
		}

	}()

	return replyChan
}
Example #10
0
// NewNimbusioWriter returns an entity that implements the NimbusioWriter interface
func NewNimbusioWriter() (NimbusioWriter, error) {
	var err error
	var state writerState
	state.SegmentMap = make(map[segmentKey]segmentMapEntry)

	if state.NodeIDMap, err = centraldb.GetNodeIDMap(); err != nil {
		return nil, fmt.Errorf("centraldb.GetNodeIDMap() failed %s", err)
	}

	if state.FileSpaceInfo, err = tools.NewFileSpaceInfo(nodedb.NodeDB); err != nil {
		return nil, err
	}

	if state.ValueFile, err = NewOutputValueFile(state.FileSpaceInfo); err != nil {
		return nil, err
	}

	writerChan := make(chan interface{}, writerChanCapacity)
	state.WriterChan = nimbusioWriterChan(writerChan)

	if state.StatGrabber, err = gostatgrabber.NewStatGrabber(); err != nil {
		return nil, fmt.Errorf("NewStatGrabber failed %s", err)
	}

	startSyncTimer(&state)

	go func() {
		for item := range writerChan {
			switch request := item.(type) {
			case requestSync:
				handleSync(&state)
			case requestStartSegment:
				handleStartSegment(&state, request)
			case requestStoreSequence:
				handleStoreSequence(&state, request)
			case requestCancelSegment:
				handleCancelSegment(&state, request)
			case requestWaitSyncForFinishSegment:
				handleWaitSyncForFinishSegment(&state, request)
			case requestFinishSegment:
				handleFinishSegment(&state, request)
			case requestDestroyKey:
				handleDestroyKey(&state, request)
			case requestStartConjoinedArchive:
				handleStartConjoinedArchive(&state, request)
			case requestAbortConjoinedArchive:
				handleAbortConjoinedArchive(&state, request)
			case requestFinishConjoinedArchive:
				handleFinishConjoinedArchive(&state, request)
			default:
				fog.Error("unknown request type %T", item)
			}
		}
	}()

	return state.WriterChan, nil
}
Example #11
0
func (s serverImpl) handleAll(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "GET":
		s.handleGET(w, req)
	case "POST":
		s.handlePOST(w, req)
	default:
		fog.Error("(%s) unknown method %s %s", s.Name, req.Method, req.URL)
	}
}
Example #12
0
func handleSync(state *writerState) {
	if state.SyncTimer != nil {
		state.SyncTimer.Stop()
	}
	state.SyncTimer = nil
	if err := state.ValueFile.Sync(); err != nil {
		fog.Error("sync failed %s", err)
	}

	for _, request := range state.WaitSyncRequests {
		state.WriterChan <- request
	}
	state.WaitSyncRequests = nil

	startSyncTimer(state)
}
Example #13
0
func NewMessageHandler() chan<- types.Message {
	var nimbusioWriter writer.NimbusioWriter
	var replyChan chan<- *reply.ReplyMessage
	var err error

	messageChan := make(chan types.Message, messageChanCapacity)

	if err = nodedb.Initialize(); err != nil {
		fog.Critical("NewMessageHandler: nodedb.Initialize failed %s", err)
	}

	if nimbusioWriter, err = writer.NewNimbusioWriter(); err != nil {
		fog.Critical("NewMessageHandler: NewNimbusioWriter() failed %s", err)
	}

	replyChan = reply.NewReplyHandler()

	dispatchTable := map[string]messageHandler{
		"archive-key-entire":       handleArchiveKeyEntire,
		"archive-key-start":        handleArchiveKeyStart,
		"archive-key-next":         handleArchiveKeyNext,
		"archive-key-final":        handleArchiveKeyFinal,
		"archive-key-cancel":       handleArchiveKeyCancel,
		"destroy-key":              handleDestroyKey,
		"start-conjoined-archive":  handleStartConjoinedArchive,
		"abort-conjoined-archive":  handleAbortConjoinedArchive,
		"finish-conjoined-archive": handleFinishConjoinedArchive}

	go func() {
		for message := range messageChan {
			handler, ok := dispatchTable[message.Type]
			if !ok {
				fog.Error("Unknown message type %s %s",
					message.Type, message.Marshalled)
				continue
			}
			go handler(message, nimbusioWriter, replyChan)
		}
	}()

	return messageChan
}
Example #14
0
func testGET(serviceDomain string, useTLS bool) {

	fog.Debug("start get")

	client, scheme := getClient(useTLS)

	url := fmt.Sprintf("%s://%s/admin", scheme, serviceDomain)
	request, err := http.NewRequest("GET", url, nil)
	if err != nil {
		fog.Critical("NewRequest (GET) failed %s", err)
	}
	response, err := client.Do(request)
	if err != nil {
		fog.Error("get %s", err)
		return
	}

	fog.Debug("reading GET response body")
	tools.ReadAndDiscard(response.Body)
	response.Body.Close()

	fog.Debug("finished get %s", response.Status)
}
Example #15
0
// handleConnection manages one HTTP connection
// expected to be run in a goroutine
func handleConnection(router routing.Router, conn net.Conn) {
	defer conn.Close()
	const bufferSize = 64 * 1024
	var err error

	requestID, err := tools.CreateUUID()
	if err != nil {
		fog.Error("%s tools.CreateUUID(): %s", conn.RemoteAddr().String(), err)
		return
	}
	fog.Info("%s starts %s", requestID, conn.RemoteAddr().String())

	request, err := http.ReadRequest(bufio.NewReaderSize(conn, bufferSize))
	if err != nil {
		fog.Error("%s %s ReadRequest failed: %s", requestID,
			conn.RemoteAddr().String(), err)
		fog.Info("%s aborts", requestID)
		return
	}

	// change the URL to point to our internal host
	request.URL.Host, err = router.Route(requestID, request)
	if err != nil {
		routerErr, ok := err.(routing.RouterError)
		if ok {
			fog.Error("%s %s, %s router error: %s",
				requestID, request.Method, request.URL, err)
			sendErrorReply(conn, routerErr.HTTPCode(), routerErr.ErrorMessage())
		} else {
			fog.Error("%s %s, %s Unexpected error type: %T %s",
				requestID, request.Method, request.URL, err, err)
		}
		fog.Info("%s aborts", requestID)
		return
	}
	request.URL.Scheme = "http"

	// heave the incoming RequestURI: can't be set in a client request
	request.RequestURI = ""

	modifyHeaders(request, conn.RemoteAddr().String(), requestID)
	fog.Debug("%s routing %s %s", requestID, request.Method, request.URL)

	// TODO: cache the connection to the internal server
	internalConn, err := net.Dial("tcp", request.URL.Host)
	if err != nil {
		fog.Error("%s %s, %s unable to dial internal server: %s",
			requestID, request.Method, request.URL, err)
		sendErrorReply(conn, http.StatusInternalServerError, err.Error())
		fog.Info("%s aborts", requestID)
		return
	}
	defer internalConn.Close()

	err = request.Write(bufio.NewWriterSize(internalConn, bufferSize))
	if err != nil {
		fog.Error("%s %s, %s request.Write: %s",
			requestID, request.Method, request.URL, err)
		sendErrorReply(conn, http.StatusInternalServerError, err.Error())
		fog.Info("%s aborts", requestID)
		return
	}
	request.Body.Close()

	response, err := http.ReadResponse(bufio.NewReaderSize(internalConn, bufferSize),
		request)
	if err != nil {
		fog.Error("%s %s, %s http.ReadResponse: %s",
			requestID, request.Method, request.URL, err)
		sendErrorReply(conn, http.StatusInternalServerError, err.Error())
		fog.Info("%s aborts", requestID)
		return
	}

	if err := response.Write(bufio.NewWriterSize(conn, bufferSize)); err != nil {
		fog.Error("%s %s, %s error sending response: %s",
			requestID, request.Method, request.URL, err)
	}
	response.Body.Close()

	fog.Info("%s ends (%d) %s", requestID, response.StatusCode, response.Status)
}
Example #16
0
// Error prepends literal 'ERROR' to log message
// program continues to execute
func (l logData) Error(text string, args ...interface{}) {
	fog.Error(l.prefix()+text, args...)
}
Example #17
0
func handleArchiveKeyEntire(message types.Message,
	nimbusioWriter writer.NimbusioWriter,
	replyChan chan<- *reply.ReplyMessage) {

	var archiveKeyEntire msg.ArchiveKeyEntire
	var md5Digest []byte
	var err error
	var valueFileID uint32

	archiveKeyEntire, err = msg.UnmarshalArchiveKeyEntire(message.Marshalled)
	if err != nil {
		fog.Error("UnmarshalArchiveKeyEntire failed %s", err)
		return
	}

	replyMessage := reply.NewReplyMessage("archive-key-final", message.ID,
		archiveKeyEntire.UserRequestID, archiveKeyEntire.ReturnAddress)

	lgr := logger.NewLogger(archiveKeyEntire.UserRequestID, archiveKeyEntire.UnifiedID,
		archiveKeyEntire.ConjoinedPart, archiveKeyEntire.SegmentNum, archiveKeyEntire.Key)
	lgr.Info("archive-key-entire")

	if archiveKeyEntire.SegmentSize != uint64(len(message.Data)) {
		lgr.Error("size mismatch (%d != %d)", archiveKeyEntire.SegmentSize,
			len(message.Data))
		replyMessage.Error("size-mismatch", "segment size does not match expected value")
		replyChan <- replyMessage
		return
	}

	md5Digest, err = base64.StdEncoding.DecodeString(archiveKeyEntire.EncodedSegmentMD5Digest)
	if err != nil {
		lgr.Error("base64.StdEncoding.DecodeString %s", err)
		replyMessage.Error("base64", "base64.StdEncoding.DecodeString")
		replyChan <- replyMessage
		return
	}

	if !MD5DigestMatches(message.Data, md5Digest) {
		lgr.Error("md5 mismatch")
		replyMessage.Error("md5-mismatch", "segment md5 does not match expected value")
		replyChan <- replyMessage
		return
	}

	err = nimbusioWriter.StartSegment(archiveKeyEntire.UserRequestID, archiveKeyEntire.Segment,
		archiveKeyEntire.NodeNames)
	if err != nil {
		lgr.Error("StartSegment: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	valueFileID, err = nimbusioWriter.StoreSequence(
		archiveKeyEntire.UserRequestID,
		archiveKeyEntire.Segment,
		archiveKeyEntire.Sequence, message.Data)
	if err != nil {
		lgr.Error("StoreSequence: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	metaData := msg.GetMetaFromJSON(message.Marshalled)

	err = nimbusioWriter.FinishSegment(archiveKeyEntire.UserRequestID,
		archiveKeyEntire.Segment,
		archiveKeyEntire.File,
		metaData,
		valueFileID)
	if err != nil {
		lgr.Error("FinishSegment: %s", err)
		replyMessage.Error("error", err.Error())
		replyChan <- replyMessage
		return
	}

	replyMessage.Success()
	replyChan <- replyMessage
	return
}
Example #18
0
// Route reads a request and decides where it should go <host:port>
func (router *routerImpl) Route(requestID string, req *http.Request) (string, error) {
	var err error

	hostName := req.Host

	router.requestCounter += 1
	fog.Debug("%s host=%s, method=%s, URL=%s", requestID, hostName, req.Method,
		req.URL)

	// TODO: be able to handle http requests from http 1.0 clients w/o a
	// host header to at least the website, if nothing else.

	if hostName == "" {
		return "", routerErrorImpl{httpCode: http.StatusBadRequest,
			errorMessage: "HOST header not found"}
	}
	routingHostName := strings.Split(hostName, ":")[0]
	if !strings.HasSuffix(routingHostName, serviceDomain) {
		return "", routerErrorImpl{httpCode: http.StatusNotFound,
			errorMessage: fmt.Sprintf("Invalid HOST '%s'", routingHostName)}
	}

	var routingMethod string
	var routedHost string

	if routingHostName == serviceDomain {
		// this is not a request specific to any particular collection
		// TODO: figure out how to route these requests.
		// in production, this might not matter.
		routingMethod = "management API"
		routedHost = router.managmentAPIDests.Next()
		fog.Debug("%s %s routed to %s by %s", requestID, req.URL.Path,
			routedHost, routingMethod)
		return routedHost, nil
	}

	destPort, ok := destPortMap[req.Method]
	if !ok {
		return "", routerErrorImpl{httpCode: http.StatusBadRequest,
			errorMessage: fmt.Sprintf("Unknown method '%s'", req.Method)}
	}

	collectionName := parseCollectionFromHostName(routingHostName)
	if collectionName == "" {
		return "", routerErrorImpl{httpCode: http.StatusNotFound,
			errorMessage: fmt.Sprintf("Unparseable host name '%s'", hostName)}
	}

	hostsForCollection, err := router.centralDB.GetHostsForCollection(collectionName)
	if err != nil {
		fog.Error("database error: collection '%s' %s", collectionName, err)
		return "", routerErrorImpl{httpCode: http.StatusInternalServerError,
			errorMessage: fmt.Sprintf("database error: collection '%s'",
				collectionName)}
	}

	if len(hostsForCollection) == 0 {
		return "", routerErrorImpl{httpCode: http.StatusNotFound,
			errorMessage: fmt.Sprintf("no hosts for collection '%s'",
				collectionName)}
	}

	availableHosts, err := router.availability.AvailableHosts(
		hostsForCollection, destPort)
	if err != nil {
		return "", routerErrorImpl{httpCode: http.StatusInternalServerError,
			errorMessage: fmt.Sprintf("collection '%s': %s", collectionName, err)}
	}
	if len(availableHosts) == 0 {
		// XXX: the python web_director retries here, after a delay.
		// IMO, that's what HTTP Status 503 is for
		return "", routerErrorImpl{httpCode: http.StatusServiceUnavailable,
			errorMessage: fmt.Sprintf("no hosts available for collection '%s'",
				collectionName)}
	}

	switch {
	case alwaysRouteToFirstNode:
		routingMethod = "NIMBUSIO_WEB_DIRECTOR_ALWAYS_FIRST_NODE"
		routedHost = availableHosts[0]
	case (req.Method == "GET" || req.Method == "HEAD") &&
		strings.HasPrefix(req.URL.Path, "/data/") &&
		len(req.URL.Path) > len("/data/"):
		routedHost, err = consistentHashDest(hostsForCollection, availableHosts,
			collectionName, req.URL.Path)
		if err != nil {
			return "", routerErrorImpl{httpCode: http.StatusInternalServerError,
				errorMessage: fmt.Sprintf("collection '%s': %s", collectionName, err)}
		}
		routingMethod = "hash"
	default:
		if router.roundRobinCounter == 0 {
			// start the round robin dispatcher at a random number, so all the
			// workers don't start on the same point.
			n, err := rand.Int(rand.Reader, big.NewInt(int64(len(hostsForCollection))))
			if err != nil {
				return "", routerErrorImpl{httpCode: http.StatusInternalServerError,
					errorMessage: fmt.Sprintf("collection '%s': %s", collectionName, err)}
			}
			router.roundRobinCounter = n.Uint64()
		} else {
			router.roundRobinCounter += 1
		}

		// XXX: the python version works with hostsForCollection and then tries
		// to find one in availableHosts. IMO, assuming the group of
		// available hosts is fairly stable, we get the same result working
		// strictly with availableHosts
		i := int(router.roundRobinCounter % uint64(len(availableHosts)))
		routedHost = availableHosts[i]
		routingMethod = "round robin"
	}

	fog.Debug("%s %s %s routed to %s by %s", requestID, collectionName,
		req.URL.Path, routedHost, routingMethod)

	return fmt.Sprintf("%s:%s", routedHost, destPort), nil
}
Example #19
0
// main entry point for webdirector
func main() {
	var err error

	fog.Info("program starts")

	// set up a signal handling channel
	signalChannel := make(chan os.Signal)
	signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)

	if profilePath := os.Getenv("WEBDIRECTOR_CPU_PROFILE_PATH"); profilePath != "" {
		profileFile, err := os.Create(profilePath)
		if err != nil {
			fog.Critical("os.Create(%s) failed %s", profilePath, err)
		}
		fog.Info("writing CPU profile data to %s", profilePath)
		pprof.StartCPUProfile(profileFile)
		defer pprof.StopCPUProfile()
	}

	useTLS := os.Getenv("NIMBUS_IO_SERVICE_SSL") == "1"
	fog.Info("TLS = %t", useTLS)

	listenAddress, err := getListenAddress()
	if err != nil {
		fog.Critical("error getListenAddress %s", err)
	}
	fog.Info("webdirector listens to %s", listenAddress)

	var listener net.Listener
	if useTLS {
		if listener, err = getTLSListener(listenAddress); err != nil {
			fog.Critical("Unable to create TLS listener: %s", err)
		}
	} else {
		if listener, err = getTCPListener(listenAddress); err != nil {
			fog.Critical("Unable to create TCP listener: %s", err)
		}
	}

	managmentAPIDests, err := mgmtapi.NewManagementAPIDestinations()
	if err != nil {
		fog.Critical("NewManagementAPIDestinations: %s", err)
	}

	centralDB := centraldb.NewCentralDB()

	availableHosts, err := avail.NewAvailability()
	if err != nil {
		fog.Critical("NewAvailability: %s", err)
	}

	router := routing.NewRouter(managmentAPIDests, centralDB, availableHosts)

	fog.Info("NIMBUS_IO_SERVICE_DOMAIN = '%s'",
		os.Getenv("NIMBUS_IO_SERVICE_DOMAIN"))
	fog.Info("NIMBUSIO_WEB_PUBLIC_READER_PORT = '%s'",
		os.Getenv("NIMBUSIO_WEB_PUBLIC_READER_PORT"))
	fog.Info("NIMBUSIO_WEB_WRITER_PORT = '%s'",
		os.Getenv("NIMBUSIO_WEB_WRITER_PORT"))

	listenerChan := make(chan net.Conn, listenerChanCapacity)
	go func() {
		for {
			connection, err := listener.Accept()
			if err != nil {
				fog.Error("listener.Accept() %s", err)
				close(listenerChan)
				break
			}
			listenerChan <- connection
		}
	}()

	for running := true; running; {
		select {
		case signal := <-signalChannel:
			fog.Info("terminated by signal: %v", signal)
			running = false
		case conn, ok := <-listenerChan:
			if ok {
				fog.Info("connection from %s", conn.RemoteAddr().String())
				go handleConnection(router, conn)
			} else {
				running = false
			}
		}
	}
	listener.Close()

	fog.Info("program terminates")
}