Exemplo n.º 1
0
    def teardown_infra(
        self,
        project: str,
        tables: Sequence[Union[FeatureTable, FeatureView]],
        entities: Sequence[Entity],
    ) -> None:
        self.online_store.teardown(self.repo_config, tables, entities)

        if (self.repo_config.feature_server is not None
                and self.repo_config.feature_server.enabled):
            _logger.info("Tearing down feature server...")
            resource_name = _get_lambda_name(project)
            lambda_client = boto3.client("lambda")
            api_gateway_client = boto3.client("apigatewayv2")

            function = aws_utils.get_lambda_function(lambda_client,
                                                     resource_name)

            if function is not None:
                _logger.info("  Tearing down AWS Lambda...")
                aws_utils.delete_lambda_function(lambda_client, resource_name)

            api = aws_utils.get_first_api_gateway(api_gateway_client,
                                                  resource_name)
            if api is not None:
                _logger.info("  Tearing down AWS API Gateway...")
                aws_utils.delete_api_gateway(api_gateway_client, api["ApiId"])
Exemplo n.º 2
0
    def get_feature_server_endpoint(self) -> Optional[str]:
        project = self.repo_config.project
        resource_name = self._get_lambda_name(project)
        api_gateway_client = boto3.client("apigatewayv2")
        api = aws_utils.get_first_api_gateway(api_gateway_client, resource_name)

        if not api:
            return None

        api_id = api["ApiId"]
        lambda_client = boto3.client("lambda")
        region = lambda_client.meta.region_name
        return f"https://{api_id}.execute-api.{region}.amazonaws.com"
Exemplo n.º 3
0
    def _deploy_feature_server(self, project: str, image_uri: str):
        _logger.info("Deploying feature server...")

        if not self.repo_config.repo_path:
            raise RepoConfigPathDoesNotExist()
        with open(self.repo_config.repo_path / "feature_store.yaml",
                  "rb") as f:
            config_bytes = f.read()
            config_base64 = base64.b64encode(config_bytes).decode()

        resource_name = _get_lambda_name(project)
        lambda_client = boto3.client("lambda")
        api_gateway_client = boto3.client("apigatewayv2")
        function = aws_utils.get_lambda_function(lambda_client, resource_name)

        if function is None:
            # If the Lambda function does not exist, create it.
            _logger.info("  Creating AWS Lambda...")
            assert isinstance(self.repo_config.feature_server,
                              AwsLambdaFeatureServerConfig)
            lambda_client.create_function(
                FunctionName=resource_name,
                Role=self.repo_config.feature_server.execution_role_name,
                Code={"ImageUri": image_uri},
                PackageType="Image",
                MemorySize=1769,
                Environment={
                    "Variables": {
                        FEATURE_STORE_YAML_ENV_NAME: config_base64,
                        FEAST_USAGE: "False",
                    }
                },
                Tags={
                    "feast-owned": "True",
                    "project": project,
                    "feast-sdk-version": get_version(),
                },
            )
            function = aws_utils.get_lambda_function(lambda_client,
                                                     resource_name)
            if not function:
                raise AwsLambdaDoesNotExist(resource_name)
        else:
            # If the feature_store.yaml has changed, need to update the environment variable.
            env = function.get("Environment", {}).get("Variables", {})
            if env.get(FEATURE_STORE_YAML_ENV_NAME) != config_base64:
                # Note, that this does not update Lambda gracefully (e.g. no rolling deployment).
                # It's expected that feature_store.yaml is not regularly updated while the lambda
                # is serving production traffic. However, the update in registry (e.g. modifying
                # feature views, feature services, and other definitions does not update lambda).
                _logger.info("  Updating AWS Lambda...")

                lambda_client.update_function_configuration(
                    FunctionName=resource_name,
                    Environment={
                        "Variables": {
                            FEATURE_STORE_YAML_ENV_NAME: config_base64
                        }
                    },
                )

        api = aws_utils.get_first_api_gateway(api_gateway_client,
                                              resource_name)
        if not api:
            # If the API Gateway doesn't exist, create it
            _logger.info("  Creating AWS API Gateway...")
            api = api_gateway_client.create_api(
                Name=resource_name,
                ProtocolType="HTTP",
                Target=function["FunctionArn"],
                RouteKey="POST /get-online-features",
                Tags={
                    "feast-owned": "True",
                    "project": project,
                    "feast-sdk-version": get_version(),
                },
            )
            if not api:
                raise AwsAPIGatewayDoesNotExist(resource_name)
            # Make sure to give AWS Lambda a permission to be invoked by the newly created API Gateway
            api_id = api["ApiId"]
            region = lambda_client.meta.region_name
            account_id = aws_utils.get_account_id()
            lambda_client.add_permission(
                FunctionName=function["FunctionArn"],
                StatementId=str(uuid.uuid4()),
                Action="lambda:InvokeFunction",
                Principal="apigateway.amazonaws.com",
                SourceArn=
                f"arn:aws:execute-api:{region}:{account_id}:{api_id}/*/*/get-online-features",
            )
Exemplo n.º 4
0
    def update_infra(
        self,
        project: str,
        tables_to_delete: Sequence[Union[FeatureTable, FeatureView]],
        tables_to_keep: Sequence[Union[FeatureTable, FeatureView]],
        entities_to_delete: Sequence[Entity],
        entities_to_keep: Sequence[Entity],
        partial: bool,
    ):
        self.online_store.update(
            config=self.repo_config,
            tables_to_delete=tables_to_delete,
            tables_to_keep=tables_to_keep,
            entities_to_keep=entities_to_keep,
            entities_to_delete=entities_to_delete,
            partial=partial,
        )

        if self.repo_config.feature_server and self.repo_config.feature_server.enabled:
            if not enable_aws_lambda_feature_server(self.repo_config):
                raise ExperimentalFeatureNotEnabled(FLAG_AWS_LAMBDA_FEATURE_SERVER_NAME)

            # Since the AWS Lambda feature server will attempt to load the registry, we
            # only allow the registry to be in S3.
            registry_path = (
                self.repo_config.registry
                if isinstance(self.repo_config.registry, str)
                else self.repo_config.registry.path
            )
            registry_store_class = get_registry_store_class_from_scheme(registry_path)
            if registry_store_class != S3RegistryStore:
                raise IncompatibleRegistryStoreClass(
                    registry_store_class.__name__, S3RegistryStore.__name__
                )

            image_uri = self._upload_docker_image(project)
            _logger.info("Deploying feature server...")

            if not self.repo_config.repo_path:
                raise RepoConfigPathDoesNotExist()
            with open(self.repo_config.repo_path / "feature_store.yaml", "rb") as f:
                config_bytes = f.read()
                config_base64 = base64.b64encode(config_bytes).decode()

            resource_name = self._get_lambda_name(project)
            lambda_client = boto3.client("lambda")
            api_gateway_client = boto3.client("apigatewayv2")
            function = aws_utils.get_lambda_function(lambda_client, resource_name)

            if function is None:
                # If the Lambda function does not exist, create it.
                _logger.info("  Creating AWS Lambda...")
                lambda_client.create_function(
                    FunctionName=resource_name,
                    Role=self.repo_config.feature_server.execution_role_name,
                    Code={"ImageUri": image_uri},
                    PackageType="Image",
                    MemorySize=1769,
                    Environment={
                        "Variables": {
                            FEATURE_STORE_YAML_ENV_NAME: config_base64,
                            FEAST_USAGE: "False",
                        }
                    },
                    Tags={
                        "feast-owned": "True",
                        "project": project,
                        "feast-sdk-version": get_version(),
                    },
                )
                function = aws_utils.get_lambda_function(lambda_client, resource_name)
                if not function:
                    raise AwsLambdaDoesNotExist(resource_name)
            else:
                # If the feature_store.yaml has changed, need to update the environment variable.
                env = function.get("Environment", {}).get("Variables", {})
                if env.get(FEATURE_STORE_YAML_ENV_NAME) != config_base64:
                    # Note, that this does not update Lambda gracefully (e.g. no rolling deployment).
                    # It's expected that feature_store.yaml is not regularly updated while the lambda
                    # is serving production traffic. However, the update in registry (e.g. modifying
                    # feature views, feature services, and other definitions does not update lambda).
                    _logger.info("  Updating AWS Lambda...")

                    lambda_client.update_function_configuration(
                        FunctionName=resource_name,
                        Environment={
                            "Variables": {FEATURE_STORE_YAML_ENV_NAME: config_base64}
                        },
                    )

            api = aws_utils.get_first_api_gateway(api_gateway_client, resource_name)
            if not api:
                # If the API Gateway doesn't exist, create it
                _logger.info("  Creating AWS API Gateway...")
                api = api_gateway_client.create_api(
                    Name=resource_name,
                    ProtocolType="HTTP",
                    Target=function["FunctionArn"],
                    RouteKey="POST /get-online-features",
                    Tags={
                        "feast-owned": "True",
                        "project": project,
                        "feast-sdk-version": get_version(),
                    },
                )
                if not api:
                    raise AwsAPIGatewayDoesNotExist(resource_name)
                # Make sure to give AWS Lambda a permission to be invoked by the newly created API Gateway
                api_id = api["ApiId"]
                region = lambda_client.meta.region_name
                account_id = aws_utils.get_account_id()
                lambda_client.add_permission(
                    FunctionName=function["FunctionArn"],
                    StatementId=str(uuid.uuid4()),
                    Action="lambda:InvokeFunction",
                    Principal="apigateway.amazonaws.com",
                    SourceArn=f"arn:aws:execute-api:{region}:{account_id}:{api_id}/*/*/get-online-features",
                )