192 lines
5.7 KiB
C++
Raw Permalink Normal View History

2019-02-09 15:45:07 +00:00
/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
*
* Copyright (C) 2019 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2019-02-11 07:47:26 -08:00
/*
* This program reads one line at a time on standard input, expecting
* a specially formatted hostname from Apache's RewriteMap and uses
* that to look up a build URL which it emits on standard output.
*/
2019-02-09 15:45:07 +00:00
#include <pthread.h>
#include <cpprest/http_client.h>
2019-02-10 10:04:44 -08:00
#include <bits/stdc++.h>
2019-02-09 15:45:07 +00:00
using namespace std;
2019-02-11 08:39:01 -08:00
vector<string> split(const string &in, char delim)
2019-02-09 12:53:17 -08:00
{
istringstream stream(in);
vector<string> parts;
string part;
2019-02-11 08:39:01 -08:00
while (getline(stream, part, delim)) {
2019-02-09 12:53:17 -08:00
parts.push_back(part);
}
return parts;
}
2019-02-11 07:47:26 -08:00
// An LRU cache of hostname->URL mappings.
2019-02-10 10:04:44 -08:00
class Cache {
2019-02-10 10:09:08 -08:00
// A queue of hostname, URL pairs. The head of the queue is always
// the most recently accessed entry, the tail is the least.
list<pair<const string, const string>> queue;
2019-02-10 10:09:08 -08:00
// A map of hostname -> iterator that points into the queue, for
// quick lookup.
unordered_map<string, list<pair<const string, const string>>::iterator> map;
2019-02-10 10:09:08 -08:00
// The maximum size of the cache.
const uint32_t size;
2019-02-10 10:04:44 -08:00
public:
Cache(uint s)
: queue {}, map {}, size{s}
{ }
2019-02-10 10:04:44 -08:00
// Lookup the hostname in the cache and return the URL if present.
// If the entry is present, it is moved to the head of the queue.
std::optional<const string> get(const string &key)
2019-02-10 10:04:44 -08:00
{
auto location = map.find(key);
2019-02-10 10:04:44 -08:00
if (location == map.end())
return {};
2019-02-10 10:04:44 -08:00
auto val = *(location->second);
queue.splice(queue.begin(), queue, location->second);
2019-02-10 10:04:44 -08:00
return val.second;
}
2019-02-10 10:09:08 -08:00
// Add an entry to the cache. If the cache is full, drop the least
// recently used entry.
void put(const string &key, const string &value)
2019-02-10 10:04:44 -08:00
{
auto location = map.find(key);
2019-02-10 10:04:44 -08:00
if (location != map.end())
return;
if (queue.size() == size) {
auto last = queue.back();
2019-02-10 10:04:44 -08:00
queue.pop_back();
map.erase(last.first);
}
queue.push_front(make_pair(key, value));
map[key] = queue.begin();
}
};
class ClientCache {
unordered_map<string, web::http::client::http_client> clients;
public:
ClientCache() : clients{} { }
web::http::client::http_client get(const string &key)
{
auto location = clients.find(key);
if (location == clients.end()) {
auto value = web::http::client::http_client(key);
clients.insert(make_pair(key, value));
return value;
}
return location->second;
}
};
2019-02-10 10:04:44 -08:00
int main(int, char**)
2019-02-09 15:45:07 +00:00
{
2019-02-11 08:39:01 -08:00
string input;
Cache cache{1024};
ClientCache clients;
2019-02-11 07:47:26 -08:00
// For each request apache receieves, it sends us the HTTP host name
// on standard input. We use that to look up the build URL and emit
// it on standard output. Apache will send us one request at a time
// (protected by an internal mutex) and expect exactly one line of
// output for each.
// Expected input:
// https://zuul.opendev.org site.167715b656ee4504baa940c5bd9f3821.openstack.preview.opendev.org
// https://zuul.opendev.org javascript_content.7cf36db4b8574ef6a5ba1a64cb827741.zuul.preview.opendev.org
2019-02-11 08:39:01 -08:00
while (getline(cin, input)) {
// Split the input into api_url, hostname
auto parts = split(input, ' ');
if (parts.size() != 2) {
cout << "NULL" << endl;
2019-02-11 08:39:01 -08:00
continue;
}
auto api_url = parts[0];
auto hostname = parts[1];
2019-02-10 10:04:44 -08:00
2019-02-11 07:47:26 -08:00
// If we have the value in the cache, return it.
if (auto val = cache.get(hostname)) {
cout << val.value() << endl;
2019-02-10 10:04:44 -08:00
continue;
}
2019-02-09 12:53:17 -08:00
2019-02-11 07:47:26 -08:00
// We use the first three parts of the hostname to look up the
// build url.
2019-02-11 08:39:01 -08:00
parts = split(hostname, '.');
2019-02-09 12:53:17 -08:00
if (parts.size() < 3) {
cout << "NULL" << endl;
2019-02-09 12:53:17 -08:00
continue;
}
auto artifact = parts[0];
auto buildid = parts[1];
auto tenant = parts[2];
2019-02-09 12:53:17 -08:00
try {
2019-02-11 07:47:26 -08:00
// Use the Zuul API to look up the artifact URL.
auto client = clients.get(api_url);
auto uri = web::uri_builder("api/tenant");
uri.append_path(tenant);
uri.append_path("build");
uri.append_path(buildid);
auto response = client.request(
web::http::methods::GET, uri.to_string()).get();
// body is a web::json::value
auto body = response.extract_json().get();
auto artifacts = body["artifacts"].as_array();
string artifact_url = "NULL";
for (uint i = 0; i < artifacts.size(); i++) {
if (artifacts[i].has_field("metadata") &&
artifacts[i]["metadata"].has_field("type") &&
artifacts[i]["metadata"]["type"].as_string() == artifact &&
artifacts[i].has_field("url")) {
artifact_url = artifacts[i]["url"].as_string();
}
}
// The apache config is guaranteed to add a / to this, so avoid
// double slashes on the end.
if (artifact_url.back() == '/') {
artifact_url.pop_back();
}
cout << artifact_url << endl;
cache.put(hostname, artifact_url);
} catch (...) {
2019-02-11 07:47:26 -08:00
// If anything goes wrong, we still need to return only a single
// string to apache, and recover for the next request, so we
// have a general exception handler here.
cout << "NULL" << endl;
}
2019-02-09 12:53:17 -08:00
}
2019-02-09 15:45:07 +00:00
}