Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor logging and error messages in serverless #1923

Merged
merged 8 commits into from
Oct 6, 2021
3 changes: 2 additions & 1 deletion sdk/python/feast/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
# Maximum interval(secs) to wait between retries for retry function
MAX_WAIT_INTERVAL: str = "60"

AWS_LAMBDA_FEATURE_SERVER_IMAGE = "feastdev/feature-server:aws"
# Dockerhub name for the AWS Lambda feature server docker image.
AWS_LAMBDA_FEATURE_SERVER_IMAGE = "feastdev/feature-server:aws-v0.1"
felixwang9817 marked this conversation as resolved.
Show resolved Hide resolved

# feature_store.yaml environment variable name for remote feature server
FEATURE_STORE_YAML_ENV_NAME: str = "FEATURE_STORE_YAML_BASE64"
Expand Down
12 changes: 8 additions & 4 deletions sdk/python/feast/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,14 @@ def __init__(self):


class AwsLambdaDoesNotExist(Exception):
def __init__(self):
super().__init__("The created AWS Lambda function does not exist.")
def __init__(self, resource_name: str):
super().__init__(
f"The AWS Lambda function {resource_name} should have been created properly, but does not exist."
)


class AwsAPIGatewayDoesNotExist(Exception):
def __init__(self):
super().__init__("The created AWS API Gateway does not exist.")
def __init__(self, resource_name: str):
super().__init__(
f"The AWS API Gateway {resource_name} should have been created properly, but does not exist."
)
50 changes: 26 additions & 24 deletions sdk/python/feast/infra/aws.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
import logging
import os
import uuid
from datetime import datetime
Expand All @@ -9,7 +10,6 @@

from colorama import Fore, Style

from feast import __version__
from feast.constants import (
AWS_LAMBDA_FEATURE_SERVER_IMAGE,
FEAST_USAGE,
Expand All @@ -30,6 +30,7 @@
from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto
from feast.registry_store import RegistryStore
from feast.repo_config import RegistryConfig
from feast.version import get_version

try:
import boto3
Expand All @@ -38,11 +39,14 @@

raise FeastExtrasDependencyImportError("aws", str(e))

_logger = logging.getLogger(__name__)
_logger.setLevel(logging.INFO)
felixwang9817 marked this conversation as resolved.
Show resolved Hide resolved
_handler = logging.StreamHandler()
_handler.setLevel(logging.INFO)
_logger.addHandler(_handler)

felixwang9817 marked this conversation as resolved.
Show resolved Hide resolved
class AwsProvider(PassthroughProvider):
def _get_lambda_name(self, project: str):
return f"feast-python-server-{project}-{__version__.replace('+', '_').replace('.', '_')}"

class AwsProvider(PassthroughProvider):
def update_infra(
self,
project: str,
Expand All @@ -63,9 +67,8 @@ def update_infra(

if self.repo_config.feature_server and self.repo_config.feature_server.enabled:
image_uri = self._upload_docker_image(project)
print("Deploying feature server...")
_logger.info("Deploying feature server...")

assert self.repo_config.repo_path
if not self.repo_config.repo_path:
raise RepoConfigPathDoesNotExist()
with open(self.repo_config.repo_path / "feature_store.yaml", "rb") as f:
Expand All @@ -79,7 +82,7 @@ def update_infra(

if function is None:
# If the Lambda function does not exist, create it.
print(" Creating AWS Lambda...")
_logger.info(" Creating AWS Lambda...")
lambda_client.create_function(
FunctionName=resource_name,
Role=self.repo_config.feature_server.execution_role_name,
Expand All @@ -95,14 +98,12 @@ def update_infra(
Tags={
"feast-owned": "True",
"project": project,
"feast-sdk-version": __version__.replace("+", "_").replace(
".", "_"
),
"feast-sdk-version": get_version(),
},
)
function = aws_utils.get_lambda_function(lambda_client, resource_name)
if not function:
raise AwsLambdaDoesNotExist()
raise AwsLambdaDoesNotExist(resource_name)
else:
# If the feature_store.yaml has changed, need to update the environment variable.
env = function.get("Environment", {}).get("Variables", {})
Expand All @@ -111,7 +112,7 @@ def update_infra(
# 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).
print(" Updating AWS Lambda...")
_logger.info(" Updating AWS Lambda...")

lambda_client.update_function_configuration(
FunctionName=resource_name,
Expand All @@ -123,7 +124,7 @@ def update_infra(
api = aws_utils.get_first_api_gateway(api_gateway_client, resource_name)
if not api:
# If the API Gateway doesn't exist, create it
print(" Creating AWS API Gateway...")
_logger.info(" Creating AWS API Gateway...")
api = api_gateway_client.create_api(
Name=resource_name,
ProtocolType="HTTP",
Expand All @@ -132,13 +133,11 @@ def update_infra(
Tags={
"feast-owned": "True",
"project": project,
"feast-sdk-version": __version__.replace("+", "_").replace(
".", "_"
),
"feast-sdk-version": get_version(),
},
)
if not api:
raise AwsAPIGatewayDoesNotExist()
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
Expand All @@ -163,20 +162,20 @@ def teardown_infra(
self.repo_config.feature_server is not None
and self.repo_config.feature_server.enabled
):
print("Tearing down feature server...")
_logger.info("Tearing down feature server...")
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 not None:
print(" Tearing down AWS Lambda...")
_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:
print(" Tearing down AWS API Gateway...")
_logger.info(" Tearing down AWS API Gateway...")
aws_utils.delete_api_gateway(api_gateway_client, api["ApiId"])

def _upload_docker_image(self, project: str) -> str:
Expand Down Expand Up @@ -213,16 +212,16 @@ def _upload_docker_image(self, project: str) -> str:

raise DockerDaemonNotRunning()

print(
_logger.info(
f"Pulling remote image {Style.BRIGHT + Fore.GREEN}{AWS_LAMBDA_FEATURE_SERVER_IMAGE}{Style.RESET_ALL}:"
)
docker_client.images.pull(AWS_LAMBDA_FEATURE_SERVER_IMAGE)

version = __version__.replace("+", "_").replace(".", "_")
version = get_version()
repository_name = f"feast-python-server-{project}-{version}"
ecr_client = boto3.client("ecr")
try:
print(
_logger.info(
f"Creating remote ECR repository {Style.BRIGHT + Fore.GREEN}{repository_name}{Style.RESET_ALL}:"
)
response = ecr_client.create_repository(repositoryName=repository_name)
Expand All @@ -243,13 +242,16 @@ def _upload_docker_image(self, project: str) -> str:

image = docker_client.images.get(AWS_LAMBDA_FEATURE_SERVER_IMAGE)
image_remote_name = f"{repository_uri}:{version}"
print(
_logger.info(
f"Pushing local image to remote {Style.BRIGHT + Fore.GREEN}{image_remote_name}{Style.RESET_ALL}:"
)
image.tag(image_remote_name)
docker_client.api.push(repository_uri, tag=version)
return image_remote_name

def _get_lambda_name(self, project: str):
return f"feast-python-server-{project}-{get_version()}"


class S3RegistryStore(RegistryStore):
def __init__(self, registry_config: RegistryConfig, repo_path: Path):
Expand Down
5 changes: 1 addition & 4 deletions sdk/python/feast/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@


def get_version():
"""
Returns version information of the Feast Python Package
"""

"""Returns version information of the Feast Python Package."""
try:
sdk_version = pkg_resources.get_distribution("feast").version
except pkg_resources.DistributionNotFound:
Expand Down