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"])
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"
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", )
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", )