10000 add service catalog cache loading from static var by alexrashed · Pull Request #12314 · localstack/localstack · GitHub
[go: up one dir, main page]

Skip to content

add service catalog cache loading from static var #12314

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

Merged
merged 2 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ RUN --mount=type=cache,target=/root/.cache \
RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LOCALSTACK_CORE=${LOCALSTACK_BUILD_VERSION} \
make entrypoints

# Generate service catalog cache in static libs dir
RUN . .venv/bin/activate && python3 -m localstack.aws.spec

# Install packages which should be shipped by default
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/var/lib/localstack/cache \
Expand Down
35 changes: 1 addition & 34 deletions localstack-core/localstack/aws/protocol/service_router.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import logging
import os
from typing import NamedTuple, Optional, Set

import botocore
from botocore.model import ServiceModel
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.http import parse_dict_header

from localstack import config
from localstack.aws.spec import (
ServiceCatalog,
ServiceModelIdentifier,
build_service_index_cache,
load_service_index_cache,
get_service_catalog,
)
from localstack.constants import VERSION
from localstack.http import Request
from localstack.services.s3.utils import uses_host_addressing
from localstack.services.sqs.utils import is_sqs_queue_url
from localstack.utils.objects import singleton_factory
from localstack.utils.strings import to_bytes
from localstack.utils.urls import hostname_from_url

Expand Down Expand Up @@ -264,33 +258,6 @@ def legacy_rules(request: Request) -> Optional[ServiceModelIdentifier]:
return ServiceModelIdentifier("s3")


@singleton_factory
def get_service_catalog() -> ServiceCatalog:
"""Loads the ServiceCatalog (which contains all the service specs), and potentially re-uses a cached index."""
if not os.path.isdir(config.dirs.cache):
return ServiceCatalog()

try:
ls_ver = VERSION.replace(".", "_")
botocore_ver = botocore.__version__.replace(".", "_")
cache_file_name = f"service-catalog-{ls_ver}-{botocore_ver}.pickle"
cache_file = os.path.join(config.dirs.cache, cache_file_name)

if not os.path.exists(cache_file):
LOG.debug("building service catalog index cache file %s", cache_file)
index = build_service_index_cache(cache_file)
else:
LOG.debug("loading service catalog index cache file %s", cache_file)
index = load_service_index_cache(cache_file)

return ServiceCatalog(index)
except Exception:
LOG.exception(
"error while processing service catalog index cache, falling back to lazy-loaded index"
)
return ServiceCatalog()


def resolve_conflicts(
candidates: Set[ServiceModelIdentifier], request: Request
) -> ServiceModelIdentifier:
Expand Down
78 changes: 70 additions & 8 deletions localstack-core/localstack/aws/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
import json
import logging
import os
import sys
from collections import defaultdict
from functools import cached_property, lru_cache
from typing import Dict, Generator, List, Literal, NamedTuple, Optional, Tuple

import botocore
import jsonpatch
from botocore.exceptions import UnknownServiceError
from botocore.loaders import Loader, instance_cache
from botocore.model import OperationModel, ServiceModel

from localstack import config
from localstack.constants import VERSION
from localstack.utils.objects import singleton_factory

LOG = logging.getLogger(__name__)

ServiceName = str
Expand Down Expand Up @@ -265,35 +271,35 @@ def build_service_index_cache(file_path: str) -> ServiceCatalogIndex:
"""
Creates a new ServiceCatalogIndex and stores it into the given file_path.

:param file_path: the path to pickle to
:param file_path: the path to store the file to
:return: the created ServiceCatalogIndex
"""
return save_service_index_cache(LazyServiceCatalogIndex(), file_path)


def load_service_index_cache(file: str) -> ServiceCatalogIndex:
"""
Loads from the given file the pickled ServiceCatalogIndex.
Loads from the given file the stored ServiceCatalogIndex.

:param file: the file to load from
:return: the loaded ServiceCatalogIndex
"""
import pickle
import dill

with open(file, "rb") as fd:
return pickle.load(fd)
return dill.load(fd)


def save_service_index_cache(index: LazyServiceCatalogIndex, file_path: str) -> ServiceCatalogIndex:
"""
Creates from the given LazyServiceCatalogIndex a ``ServiceCatalogIndex`, pickles its contents into the given file,
Creates from the given LazyServiceCatalogIndex a ``ServiceCatalogIndex`, stores its contents into the given file,
and then returns the newly created index.

:param index: the LazyServiceCatalogIndex to store the index from.
:param file_path: the path to pickle to
:param file_path: the path to store the binary index cache file to
:return: the created ServiceCatalogIndex
"""
import pickle
import dill

cache = ServiceCatalogIndex(
service_names=index.service_names,
Expand All @@ -303,5 +309,61 @@ def save_service_index_cache(index: LazyServiceCatalogIndex, file_path: str) ->
target_prefix_index=index.target_prefix_index,
)
with open(file_path, "wb") as fd:
pickle.dump(cache, fd)
# use dill (instead of plain pickle) to avoid issues when serializing the pickle from __main__
dill.dump(cache, fd)
return cache


def _get_catalog_filename():
ls_ver = VERSION.replace(".", "_")
botocore_ver = botocore.__version__.replace(".", "_")
return f"service-catalog-{ls_ver}-{botocore_ver}.dill"


@singleton_factory
def get_service_catalog() -> ServiceCatalog:
"""Loads the ServiceCatalog (which contains all the service specs), and potentially re-uses a cached index."""

try:
catalog_file_name = _get_catalog_filename()
static_catalog_file = os.path.join(config.dirs.static_libs, catalog_file_name)

# try to load or load/build/save the service catalog index from the static libs
index = None
if os.path.exists(static_catalog_file):
# load the service catalog from the static libs dir / built at build time
LOG.debug("loading service catalog index cache file %s", static_catalog_file)
index = load_service_index_cache(static_catalog_file)
elif os.path.isdir(config.dirs.cache):
cache_catalog_file = os.path.join(config.dirs.cache, catalog_file_name)
if os.path.exists(cache_catalog_file):
LOG.debug("loading service catalog index cache file %s", cache_catalog_file)
index = load_service_index_cache(cache_catalog_file)
else:
LOG.debug("building service catalog index cache file %s", cache_catalog_file)
index = build_service_index_cache(cache_catalog_file)
return ServiceCatalog(index)
except Exception:
LOG.exception(
"error while processing service catalog index cache, falling back to lazy-loaded index"
)
return ServiceCatalog()


def main():
catalog_file_name = _get_catalog_filename()
static_catalog_file = os.path.join(config.dirs.static_libs, catalog_file_name)

if os.path.exists(static_catalog_file):
LOG.error(
"service catalog index cache file (%s) already there. aborting!", static_catalog_file
)
return 1

# load the service catalog from the static libs dir / built at build time
LOG.debug("building service catalog index cache file %s", static_catalog_file)
build_service_index_cache(static_catalog_file)


if __name__ == "__main__":
sys.exit(main())
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
connect_to,
dump_dto,
)
from localstack.aws.protocol.service_router import get_service_catalog
from localstack.aws.spec import get_service_catalog
from localstack.constants import APPLICATION_JSON, INTERNAL_AWS_ACCESS_KEY_ID
from localstack.utils.aws.arns import extract_region_from_arn
from localstack.utils.aws.client_types import ServicePrincipal
Expand Down
2 changes: 1 addition & 1 deletion localstack-core/localstack/services/s3/cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# TODO: refactor those to expose the needed methods
from localstack.aws.handlers.cors import CorsEnforcer, CorsResponseEnricher
from localstack.aws.protocol.op_router import RestServiceOperationRouter
from localstack.aws.protocol.service_router import get_service_catalog
from localstack.aws.spec import get_service_catalog
from localstack.config import S3_VIRTUAL_HOSTNAME
from localstack.http import Request, Response
from localstack.services.s3.utils import S3_VIRTUAL_HOSTNAME_REGEX
Expand Down
2 changes: 1 addition & 1 deletion localstack-core/localstack/services/s3/presigned_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
)
from localstack.aws.chain import HandlerChain
from localstack.aws.protocol.op_router import RestServiceOperationRouter
from localstack.aws.protocol.service_router import get_service_catalog
from localstack.aws.spec import get_service_catalog
from localstack.http import Request, Response
from localstack.http.request import get_raw_path
from localstack.services.s3.constants import (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from botocore.exceptions import ClientError, UnknownServiceError

from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails
from localstack.aws.protocol.service_router import get_service_catalog
from localstack.aws.spec import get_service_catalog
from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ErrorName
from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import (
FailureEvent,
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/aws/test_service_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from botocore.config import Config
from botocore.model import OperationModel, ServiceModel, Shape, StructureShape

from localstack.aws.protocol.service_router import determine_aws_service_model, get_service_catalog
from localstack.aws.protocol.service_router import determine_aws_service_model
from localstack.aws.spec import get_service_catalog
from localstack.http import Request
from localstack.utils.run import to_str

Expand Down
Loading
0