Ejemplo n.º 1
0
// InvokeChanURL invokes a remote streaming method referenced by a Lever URL.
// The URL can be either absolute (remote environment) or relative
// (same environment).
// Example:
//
// /<service>[/<resource>]/method  (relative URL)
//
// lever://<env>/<service>[/<resource>]/method  (absolute URL)
func (client *Client) InvokeChanURL(
	leverURLStr string, args ...interface{}) (stream Stream, err error) {
	leverURL, err := core.ParseLeverURL(leverURLStr)
	if err != nil {
		return nil, err
	}
	return client.invokeChanInternal(leverURL, args...)
}
Ejemplo n.º 2
0
// InvokeURL invokes a remote method referenced by a Lever URL. The URL can
// be either absolute (remote environment) or relative (same environment).
// Example:
//
// /<service>[/<resource>]/method  (relative URL)
//
// lever://<env>/<service>[/<resource>]/method  (absolute URL)
func (client *Client) InvokeURL(
	replyObj interface{}, leverURLStr string, args ...interface{}) (err error) {
	leverURL, err := core.ParseLeverURL(leverURLStr)
	if err != nil {
		return err
	}
	return client.invokeInternal(replyObj, leverURL, args...)
}
Ejemplo n.º 3
0
func extractLeverURL(ctx context.Context) (*core.LeverURL, error) {
	md, ok := metadata.FromContext(ctx)
	if !ok {
		return nil, fmt.Errorf("Metadata not found")
	}
	leverURLStr, ok := md["lever-url"]
	if !ok || len(leverURLStr) == 0 {
		return nil, fmt.Errorf("Lever URL header not found")
	}
	return core.ParseLeverURL(leverURLStr[0])
}
Ejemplo n.º 4
0
// ServeHTTP serves individual HTTP requests.
func (server *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	origin := req.Header.Get("Origin")
	if origin != "" {
		// TODO: Perhaps a lever.json config param could restrict origin
		//       to prevent CSRF.
		resp.Header().Set("Access-Control-Allow-Origin", origin)
		resp.Header().Set(
			"Access-Control-Allow-Headers",
			"Accept, Content-Type, Content-Length, Accept-Encoding, "+
				"X-CSRF-Token, Authorization")
	}
	if req.Method == "OPTIONS" {
		resp.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
		resp.WriteHeader(http.StatusOK)
		return
	}
	if req.Method != "POST" {
		resp.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	leverURLStr := fmt.Sprintf("lever://%s%s", req.Host, req.URL.Path)
	leverURL, err := core.ParseLeverURL(leverURLStr)
	if err != nil {
		logger.WithFields("err", err).Debug("Error parsing Lever URL")
		resp.WriteHeader(http.StatusBadRequest)
		return
	}

	queryValues, err := url.ParseQuery(req.URL.RawQuery)
	if err != nil {
		logger.WithFields("err", err).Debug("Error parsing URL query")
		resp.WriteHeader(http.StatusBadRequest)
		return
	}
	forceEnv := queryValues.Get("forceenv")
	if forceEnv != "" {
		leverURL.Environment = forceEnv
	}
	leverURLStr = leverURL.String()

	reader := bufferedReaderPool.Get().(*bufio.Reader)
	reader.Reset(req.Body)
	defer bufferedReaderPool.Put(reader)
	defer req.Body.Close()
	if leverapi.IsChanMethod(leverURL.Method) {
		// TODO: Byte args not supported. Any way to support that?
		//       How to delimit args from rest?
		done := false
		var line []byte
		line, err = reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				done = true
			} else {
				logger.WithFields("err", err).Error("Read error")
				return
			}
		}
		if len(line) == 0 {
			resp.WriteHeader(http.StatusBadRequest)
			return
		}
		var args []interface{}
		err = json.Unmarshal(line, &args)
		if err != nil {
			resp.WriteHeader(http.StatusBadRequest)
			logger.WithFields("err", err).Debug("Malformed JSON")
			return
		}
		var stream leverapi.Stream
		stream, err = server.leverClient.InvokeChanURL(leverURLStr, args...)
		if err != nil {
			resp.WriteHeader(http.StatusInternalServerError)
			logger.WithFields("err", err).Error("InvokeChanURL error")
			return
		}
		errCh := make(chan bool)
		workerDoneCh := make(chan struct{})
		go replyStreamWorker(stream, resp, errCh, workerDoneCh)
		if req.Header.Get("Content-Type") == "application/json" {
			for !done {
				line, err = reader.ReadBytes('\n')
				if err != nil {
					if err == io.EOF {
						done = true
					} else {
						logger.WithFields("err", err).Error("Read error")
						errCh <- true
						<-workerDoneCh
						return
					}
				}
				if len(line) > 0 {
					var msg interface{}
					err = json.Unmarshal(line, &msg)
					if err != nil {
						logger.WithFields("err", err).Debug("Malformed JSON")
						errCh <- true
						<-workerDoneCh
						return
					}
					stream.Send(msg)
				}
			}
		} else {
			for !done {
				buffer := bufferPool.Get().([]byte)
				defer bufferPool.Put(buffer)
				var size int
				size, err = reader.Read(buffer)
				if err != nil {
					if err == io.EOF {
						done = true
					}
					logger.WithFields("err", err).Error("Read error")
					errCh <- true
					<-workerDoneCh
					return
				}
				msg := buffer[:size]
				stream.Send(msg)
			}
		}
		err = stream.Close()
		if err != nil {
			logger.WithFields("err", err).Debug(
				"Stream close error")
			errCh <- true
			<-workerDoneCh
			return
		}
		errCh <- false
		<-workerDoneCh
	} else {
		buffer := bufferPool.Get().([]byte)
		defer bufferPool.Put(buffer)
		var size int
		size, err = io.ReadFull(reader, buffer)
		if err != nil {
			if err != io.EOF && err != io.ErrUnexpectedEOF {
				logger.WithFields("err", err).Error("Read error")
				return
			}
		}
		if size == maxNonChanRequestSize &&
			err != io.EOF &&
			err != io.ErrUnexpectedEOF {
			resp.WriteHeader(http.StatusBadRequest)
			_, err = resp.Write([]byte("\"Exceeded maximum request size\""))
			if err != nil {
				logger.WithFields("err", err).Debug("Write error")
			}
			return
		}
		var args []interface{}
		contentType := req.Header.Get("Content-Type")
		contentTypeSplit := strings.Split(contentType, ";")
		switch contentTypeSplit[0] {
		case "application/json":
			err = json.Unmarshal(buffer[:size], &args)
			if err != nil {
				resp.WriteHeader(http.StatusBadRequest)
				logger.WithFields("err", err).Debug("JSON unmarshal error")
				return
			}
		case "application/x-www-form-urlencoded":
			// TODO
			resp.WriteHeader(http.StatusBadRequest)
			logger.WithFields("contentType", contentType).Error(
				"Content type not yet supported")
			return
		default:
			args = make([]interface{}, 1)
			args[0] = buffer[:size]
		}
		var reply interface{}
		err = server.leverClient.InvokeURL(&reply, leverURLStr, args...)
		if err != nil {
			resp.WriteHeader(http.StatusInternalServerError)
			remoteErr, ok := err.(*leverapi.RemoteError)
			if ok {
				reply = remoteErr.Err
			} else {
				remoteByteErr, ok := err.(*leverapi.RemoteByteError)
				if ok {
					reply = remoteByteErr.Err
				} else {
					logger.WithFields("err", err).Error("Internal Lever error")
					return
				}
			}
		} else {
			resp.WriteHeader(http.StatusOK)
		}

		err = writeReply(resp, reply)
		if err != nil {
			logger.WithFields("err", err).Error("Writing reply failed")
			return
		}
	}
}
Ejemplo n.º 5
0
func (proxy *LeverProxy) handleInStream(stream *http2stream.HTTP2Stream) {
	headers := stream.GetHeaders()
	err := expectHeaders(
		headers,
		"lever-url",
		"x-lever-src-env",
		"x-lever-dest-instance-id",
		"x-lever-dest-container-id",
		"x-lever-serving-id",
		"x-lever-code-version",
		"x-lever-inst-resource-id",
		"x-lever-inst-session-id",
		"x-lever-res-resource-id",
		"x-lever-res-session-id",
	)
	if err != nil {
		proxy.inLogger.WithFields("err", err).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	leverURL, err := core.ParseLeverURL(headers["lever-url"][0])
	if err != nil {
		proxy.inLogger.WithFields(
			"err", err, "leverURL", headers["lever-url"][0]).Error(
			"Unable to parse Lever URL")
	}

	if !core.IsInternalEnvironment(leverURL.Environment) {
		err = fmt.Errorf("Cannot route to dest env")
		proxy.inLogger.WithFields(
			"err", err,
			"leverEnv", leverURL.Environment,
		).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	srcEnv := headers["x-lever-src-env"][0]
	instanceID := headers["x-lever-dest-instance-id"][0]
	containerID := headers["x-lever-dest-container-id"][0]
	servingID := headers["x-lever-serving-id"][0]
	codeVersionInt, err := strconv.Atoi(headers["x-lever-code-version"][0])
	if err != nil {
		proxy.inLogger.WithFields("err", err).Error("Cannot parse code version")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}
	codeVersion := int64(codeVersionInt)
	levInstResourceID := headers["x-lever-inst-resource-id"][0]
	levInstSessionID := headers["x-lever-inst-session-id"][0]
	levResResourceID := headers["x-lever-res-resource-id"][0]
	levResSessionID := headers["x-lever-res-session-id"][0]

	if instanceID == "" {
		instanceID, err = proxy.manager.RandomInstaceID(servingID)
		if err != nil {
			proxy.inLogger.WithFields(
				"err", err,
				"leverEnv", leverURL.Environment,
			).Error("Could not find an instanceID for provided servingID")
			stream.Write(&http2stream.MsgError{Err: err})
			return
		}
	}

	streamID := leverutil.RandomID()
	proxy.inLogger.WithFields(
		"leverURL", leverURL.String(),
		"srcEnv", srcEnv,
		"leverInstanceID", instanceID,
		"containerID", containerID,
		"servingID", servingID,
		"levInstSessionID", levInstSessionID,
		"levInstResourceID", levInstResourceID,
		"streamID", streamID,
	).Debug("Receiving stream")
	streamLogger := proxy.inLogger.WithFields("streamID", streamID)

	if !core.IsInternalEnvironment(leverURL.Environment) {
		err = fmt.Errorf("Environment not routable internally")
		streamLogger.WithFields(
			"err", err,
			"leverEnv", leverURL.Environment,
		).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	_, ownIP, instanceAddr, keepAliveFun, err :=
		proxy.manager.EnsureInfrastructureInitialized(&hostman.InstanceInfo{
			Environment:       leverURL.Environment,
			Service:           leverURL.Service,
			InstanceID:        instanceID,
			ContainerID:       containerID,
			ServingID:         servingID,
			LevInstResourceID: levInstResourceID,
			LevInstSessionID:  levInstSessionID,
		})
	if err != nil {
		streamLogger.WithFields("err", err).Error(
			"Error initializing instance")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	err = proxy.serveOut(leverURL.Environment, ownIP)
	if err != nil {
		streamLogger.WithFields("err", err).Error(
			"Error listening on env network")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	destStreamI, err := leverutil.ExpBackoff(
		func() (clientStream interface{}, err error, finalErr error) {
			clientStream, err = proxy.client.NewStream(instanceAddr)
			if err != nil {
				if strings.Contains(err.Error(), "connection refused") &&
					proxy.manager.IsInstanceAlive(servingID, instanceID) {
					// Retry.
					return nil, err, nil
				}
				if err == leverutil.ErrNotYetConstructed ||
					err == leverutil.ErrWasDestructed {
					// Retry.
					return nil, err, nil
				}
				return nil, nil, err
			}
			return clientStream, nil, nil
		}, 10*time.Millisecond, 15*time.Second)
	if err != nil {
		streamLogger.WithFields(
			"err", err,
			"instanceAddr", instanceAddr,
		).Error("Error trying to create client stream to dest")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}
	destStream := destStreamI.(*http2stream.HTTP2Stream)

	addHeaders := make(map[string][]string)
	if srcEnv != "" {
		addHeaders["x-lever-src-env"] = []string{srcEnv}
	}
	addHeaders["x-lever-internal-rpc-gateway"] = []string{
		ownIP + ":" + EnvOutListenPortFlag.Get()}

	startTime := time.Now()
	firstHeaders := true
	stream.ProxyTo(
		destStream,
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(instanceAddr)
			keepAliveFun(
				leverURL.Resource, levResResourceID, levResSessionID)
			return proxy.filterTo(&firstHeaders, addHeaders, msg)
		},
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(instanceAddr)
			keepAliveFun(
				leverURL.Resource, levResResourceID, levResSessionID)
			return noFilter(msg)
		})
	// Wait for RPC to finish.
	<-destStream.Closed()
	rpcNanos := uint64(time.Now().Sub(startTime).Nanoseconds())
	streamLogger.WithFields("rpcNanos", rpcNanos).Debug("RPC nanos")

	// Send RPC stats to fleettracker.
	// TODO: For services with high load, we should not send info on every
	//       RPC. They should be batched and sent say... every ~50ms
	//       (well below tracker tick interval).
	err = fleettracker.OnRPC(proxy.grpcPool, &fleettracker.RPCEvent{
		Environment: leverURL.Environment,
		Service:     leverURL.Service,
		ServingID:   servingID,
		CodeVersion: codeVersion,
		IsAdmin:     core.IsAdmin(leverURL),
		RpcNanos:    rpcNanos,
	})
	if err != nil {
		streamLogger.WithFields("err", err).Error(
			"Failed to send RPC stats to fleettracker")
	}
}
Ejemplo n.º 6
0
func actionStream(ctx *cli.Context) {
	leverURL, err := core.ParseLeverURL(ctx.Args().Get(0))
	if err != nil {
		logger.WithFields("err", err).Fatal("Invalid lever URL")
	}
	if leverURL.Environment == "" {
		leverURL.Environment = flagEnv
	}

	client, err := leverapi.NewClient()
	if err != nil {
		logger.WithFields("err", err).Fatal("Error creating client")
	}
	if strings.HasSuffix(leverURL.Environment, ".lever") {
		client.ForceHost = detectLeverOSIPPort()
	}
	if flagHost != "" {
		client.ForceHost = flagHost
	}

	var args []interface{}
	for index := 1; index < ctx.NArg(); index++ {
		rawArg := ctx.Args().Get(index)
		var arg interface{}
		err = json.Unmarshal([]byte(rawArg), &arg)
		if err != nil {
			logger.WithFields("err", err, "jsonArg", rawArg).Fatal(
				"Error parsing JSON arg")
		}
		args = append(args, arg)
	}
	stream, err := client.InvokeChanURL(leverURL.String(), args...)
	if err != nil {
		logger.WithFields("err", err).Fatal("Invokation error")
	}

	receiveDone := make(chan struct{})
	go func() {
		stdout := bufio.NewWriter(os.Stdout)

		for {
			var msg interface{}
			err = stream.Receive(&msg)
			if err == io.EOF {
				break
			}
			if err != nil {
				logger.WithFields("err", err).Fatal(
					"Error receiving from server")
			}

			msgBytes, isBytes := msg.([]byte)
			if isBytes {
				_, err = stdout.Write(msgBytes)
				if err != nil {
					logger.WithFields("err", err).Fatal(
						"Error writing to stdout")
				}
			} else {
				var jsonMsg []byte
				if flagPrettyPrint {
					jsonMsg, err = json.MarshalIndent(msg, "", "  ")
				} else {
					jsonMsg, err = json.Marshal(msg)
				}
				if err != nil {
					logger.WithFields("err", err).Fatal(
						"Error turning receiving msg into JSON")
				}
				stdout.Write(jsonMsg)
				stdout.WriteString("\n")
			}
		}

		err = stdout.Flush()
		if err != nil {
			logger.WithFields("err", err).Fatal("Error flushing stdout")
		}
		close(receiveDone)
	}()

	stdin := bufio.NewReader(os.Stdin)
	if flagBytes {
		chunk := make([]byte, 32*1024)
		for {
			var size int
			size, err = stdin.Read(chunk)
			if err == io.EOF {
				break
			}
			if err != nil {
				logger.WithFields("err", err).Fatal("Error reading from stdin")
			}

			err = stream.Send(chunk[:size])
			if err != nil {
				logger.WithFields("err", err).Fatal("Error sending chunk")
			}
		}
	} else {
		for {
			var line string
			line, err = stdin.ReadString('\n')
			if err == io.EOF && line == "" {
				break
			}
			if err != nil && err != io.EOF {
				logger.WithFields("err", err).Fatal("Error reading from stdin")
			}

			var jsonMsg interface{}
			err = json.Unmarshal([]byte(line), &jsonMsg)
			if err != nil {
				logger.WithFields("err", err).Fatal(
					"Error parsing JSON from stdin")
			}

			err = stream.Send(jsonMsg)
			if err != nil {
				logger.WithFields("err", err).Fatal("Error sending JSON")
			}
		}
	}

	err = stream.Close()
	if err != nil {
		logger.WithFields("err", err).Fatal("Error trying to close stream")
	}
	<-receiveDone
}
Ejemplo n.º 7
0
func actionInvoke(ctx *cli.Context) {
	leverURL, err := core.ParseLeverURL(ctx.Args().Get(0))
	if err != nil {
		logger.WithFields("err", err).Fatal("Invalid lever URL")
	}
	if leverURL.Environment == "" {
		leverURL.Environment = flagEnv
	}

	client, err := leverapi.NewClient()
	if err != nil {
		logger.WithFields("err", err).Fatal("Error creating client")
	}
	if strings.HasSuffix(leverURL.Environment, ".lever") {
		client.ForceHost = detectLeverOSIPPort()
	}
	if flagHost != "" {
		client.ForceHost = flagHost
	}

	if ctx.NArg() > 1 && ctx.Args().Get(1) == "--" {
		// Byte args case.
		// TODO: Read bytes arg from stdin.
		logger.Fatal("Bytes from stdin not yet implemented")
		return
	}

	// JSON args case.
	var args []interface{}
	for index := 1; index < ctx.NArg(); index++ {
		rawArg := ctx.Args().Get(index)
		var arg interface{}
		err = json.Unmarshal([]byte(rawArg), &arg)
		if err != nil {
			logger.WithFields("err", err, "jsonArg", rawArg).Fatal(
				"Error parsing JSON arg")
		}
		args = append(args, arg)
	}
	var reply interface{}
	err = client.InvokeURL(&reply, leverURL.String(), args...)
	if err != nil {
		logger.WithFields("err", err).Fatal("Invokation error")
	}
	bytes, isByteReply := reply.([]byte)
	if !isByteReply {
		if flagPrettyPrint {
			bytes, err = json.MarshalIndent(reply, "", "  ")
		} else {
			bytes, err = json.Marshal(reply)
		}
		if err != nil {
			logger.WithFields("err", err).Fatal(
				"Error turning reply into JSON form")
		}
	}

	stdout := bufio.NewWriter(os.Stdout)
	stdout.Write(bytes)
	if !isByteReply && flagPrettyPrint {
		stdout.WriteString("\n")
	}
	stdout.Flush()
}
Ejemplo n.º 8
0
func (proxy *LeverProxy) handleExtOutStream(
	srcEnv string, stream *http2stream.HTTP2Stream, isExtStream bool) {
	streamLogger := proxy.outLogger
	if isExtStream {
		streamLogger = proxy.extLogger
	}

	headers := stream.GetHeaders()
	err := expectHeaders(headers, "lever-url")
	if err != nil {
		streamLogger.WithFields("err", err).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	leverURL, err := core.ParseLeverURL(headers["lever-url"][0])
	if err != nil {
		proxy.inLogger.WithFields(
			"err", err, "leverURL", headers["lever-url"][0]).Error(
			"Unable to parse Lever URL")
	}

	if !core.IsInternalEnvironment(leverURL.Environment) {
		err = fmt.Errorf("Cannot route to dest env")
		streamLogger.WithFields(
			"err", err,
			"leverEnv", leverURL.Environment,
		).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	streamID := leverutil.RandomID()
	streamLogger.WithFields(
		"leverURL", leverURL.String(),
		"srcEnv", srcEnv,
		"streamID", streamID,
	).Debug("Receiving stream")
	streamLogger = streamLogger.WithFields("streamID", streamID)

	isFromExt := !core.IsInternalEnvironment(srcEnv)
	hostInfo, err := proxy.finder.GetHost(
		leverURL.Environment, leverURL.Service, leverURL.Resource, isFromExt)
	if err != nil {
		streamLogger.WithFields(
			"err", err,
			"leverURL", leverURL.String(),
		).Error("Error trying to find / allocate host for request")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	streamLogger.WithFields(
		"leverInstanceID", hostInfo.InstanceID,
		"containerID", hostInfo.ContainerID,
		"servingID", hostInfo.ServingID,
		"hostProxyAddr", hostInfo.HostAddr,
		"isNewInstance", hostInfo.IsNewInstance,
	).Debug("Routing to host proxy")
	destStream, err := proxy.client.NewStream(hostInfo.HostAddr)
	if err != nil {
		streamLogger.WithFields(
			"err", err,
			"hostProxyAddr", hostInfo.HostAddr,
		).Error("Error trying to create client stream to dest")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	addHeaders := make(map[string][]string)
	addHeaders["x-lever-src-env"] = []string{srcEnv}
	addHeaders["x-lever-dest-instance-id"] = []string{hostInfo.InstanceID}
	addHeaders["x-lever-dest-container-id"] = []string{hostInfo.ContainerID}
	addHeaders["x-lever-serving-id"] = []string{hostInfo.ServingID}
	addHeaders["x-lever-code-version"] = []string{
		strconv.Itoa(int(hostInfo.CodeVersion))}
	addHeaders["x-lever-inst-resource-id"] = []string{
		hostInfo.LevInstResourceID}
	addHeaders["x-lever-inst-session-id"] = []string{hostInfo.LevInstSessionID}
	addHeaders["x-lever-res-resource-id"] = []string{hostInfo.LevResResourceID}
	addHeaders["x-lever-res-session-id"] = []string{hostInfo.LevResSessionID}

	firstHeaders := true
	stream.ProxyTo(
		destStream,
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(hostInfo.HostAddr)
			return proxy.filterTo(&firstHeaders, addHeaders, msg)
		},
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(hostInfo.HostAddr)
			return noFilter(msg)
		})
}