1
#include <memory>
2
#include <string>
3

            
4
#include "envoy/extensions/http/cache_v2/file_system_http_cache/v3/file_system_http_cache.pb.h"
5
#include "envoy/extensions/http/cache_v2/file_system_http_cache/v3/file_system_http_cache.pb.validate.h"
6
#include "envoy/registry/registry.h"
7

            
8
#include "source/extensions/common/async_files/async_file_manager_factory.h"
9
#include "source/extensions/filters/http/cache_v2/cache_sessions.h"
10
#include "source/extensions/filters/http/cache_v2/http_cache.h"
11
#include "source/extensions/http/cache_v2/file_system_http_cache/cache_eviction_thread.h"
12
#include "source/extensions/http/cache_v2/file_system_http_cache/file_system_http_cache.h"
13

            
14
namespace Envoy {
15
namespace Extensions {
16
namespace HttpFilters {
17
namespace CacheV2 {
18
namespace FileSystemHttpCache {
19
namespace {
20

            
21
/**
22
 * Returns a copy of the original ConfigProto with a slash appended to cache_path
23
 * if one was not present.
24
 * @param original the original ConfigProto.
25
 * @return the normalized ConfigProto.
26
 */
27
48
ConfigProto normalizeConfig(const ConfigProto& original) {
28
48
  ConfigProto config = original;
29
48
  if (!absl::EndsWith(config.cache_path(), "/") && !absl::EndsWith(config.cache_path(), "\\")) {
30
2
    config.set_cache_path(absl::StrCat(config.cache_path(), "/"));
31
2
  }
32
48
  return config;
33
48
}
34

            
35
/**
36
 * A singleton that acts as a factory for generating and looking up FileSystemHttpCaches.
37
 * When given equivalent configs, the singleton returns pointers to the same cache.
38
 * When given different configs, the singleton returns different cache instances.
39
 * If given configs with the same cache_path but different configuration,
40
 * an error status is returned, as it doesn't make sense two operate two caches in the
41
 * same path with different configurations.
42
 */
43
class CacheSingleton : public Envoy::Singleton::Instance {
44
public:
45
  CacheSingleton(
46
      std::shared_ptr<Common::AsyncFiles::AsyncFileManagerFactory>&& async_file_manager_factory,
47
      Thread::ThreadFactory& thread_factory)
48
45
      : async_file_manager_factory_(async_file_manager_factory),
49
45
        cache_eviction_thread_(thread_factory) {}
50

            
51
  absl::StatusOr<std::shared_ptr<CacheSessions>>
52
  get(std::shared_ptr<CacheSingleton> singleton, const ConfigProto& non_normalized_config,
53
48
      Server::Configuration::FactoryContext& context) {
54
48
    std::shared_ptr<CacheSessions> cache;
55
48
    ConfigProto config = normalizeConfig(non_normalized_config);
56
48
    auto key = config.cache_path();
57
48
    absl::MutexLock lock(mu_);
58
48
    auto it = caches_.find(key);
59
48
    if (it != caches_.end()) {
60
2
      cache = it->second.lock();
61
2
    }
62
48
    if (!cache) {
63
46
      std::shared_ptr<Common::AsyncFiles::AsyncFileManager> async_file_manager =
64
46
          async_file_manager_factory_->getAsyncFileManager(config.manager_config());
65
46
      std::unique_ptr<FileSystemHttpCache> fs_cache = std::make_unique<FileSystemHttpCache>(
66
46
          singleton, cache_eviction_thread_, std::move(config), std::move(async_file_manager),
67
46
          context.scope());
68
46
      cache = CacheSessions::create(context, std::move(fs_cache));
69
46
      caches_[key] = cache;
70
46
    } else {
71
      // Check that the config of the cache found in the lookup table for the given path
72
      // has the same config as the config being added.
73
2
      FileSystemHttpCache& fs_cache = static_cast<FileSystemHttpCache&>(cache->cache());
74
2
      if (!Protobuf::util::MessageDifferencer::Equals(fs_cache.config(), config)) {
75
1
        return absl::InvalidArgumentError(
76
1
            fmt::format("mismatched FileSystemHttpCacheV2Config with same path\n{}\nvs.\n{}",
77
1
                        fs_cache.config().DebugString(), config.DebugString()));
78
1
      }
79
2
    }
80
47
    return cache;
81
48
  }
82

            
83
private:
84
  std::shared_ptr<Common::AsyncFiles::AsyncFileManagerFactory> async_file_manager_factory_;
85
  CacheEvictionThread cache_eviction_thread_;
86
  absl::Mutex mu_;
87
  // We keep weak_ptr here so the caches can be destroyed if the config is updated to stop using
88
  // that config of cache. The caches each keep shared_ptrs to this singleton, which keeps the
89
  // singleton from being destroyed unless it's no longer keeping track of any caches.
90
  // (The singleton shared_ptr is *only* held by cache instances.)
91
  absl::flat_hash_map<std::string, std::weak_ptr<CacheSessions>> caches_ ABSL_GUARDED_BY(mu_);
92
};
93

            
94
SINGLETON_MANAGER_REGISTRATION(file_system_http_cache_v2_singleton);
95

            
96
class FileSystemHttpCacheFactory : public HttpCacheFactory {
97
public:
98
  // From UntypedFactory
99
5
  std::string name() const override { return std::string{FileSystemHttpCache::name()}; }
100
  // From TypedFactory
101
2
  ProtobufTypes::MessagePtr createEmptyConfigProto() override {
102
2
    return std::make_unique<ConfigProto>();
103
2
  }
104
  // From HttpCacheFactory
105
  absl::StatusOr<std::shared_ptr<CacheSessions>>
106
  getCache(const envoy::extensions::filters::http::cache_v2::v3::CacheV2Config& filter_config,
107
48
           Server::Configuration::FactoryContext& context) override {
108
48
    ConfigProto config;
109
48
    RETURN_IF_NOT_OK(MessageUtil::unpackTo(filter_config.typed_config(), config));
110
48
    std::shared_ptr<CacheSingleton> caches =
111
48
        context.serverFactoryContext().singletonManager().getTyped<CacheSingleton>(
112
48
            SINGLETON_MANAGER_REGISTERED_NAME(file_system_http_cache_v2_singleton), [&context] {
113
45
              return std::make_shared<CacheSingleton>(
114
45
                  Common::AsyncFiles::AsyncFileManagerFactory::singleton(
115
45
                      &context.serverFactoryContext().singletonManager()),
116
45
                  context.serverFactoryContext().api().threadFactory());
117
45
            });
118
48
    return caches->get(caches, config, context);
119
48
  }
120
};
121

            
122
static Registry::RegisterFactory<FileSystemHttpCacheFactory, HttpCacheFactory> register_;
123

            
124
} // namespace
125
} // namespace FileSystemHttpCache
126
} // namespace CacheV2
127
} // namespace HttpFilters
128
} // namespace Extensions
129
} // namespace Envoy