// 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) } }
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) }
// 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) } }
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 }
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 }
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) }
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 }
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 }
// 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 }
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) } }
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) }
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 }
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) }
// 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) }
// 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...) }
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 }
// 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 }
// 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") }