-
Notifications
You must be signed in to change notification settings - Fork 5
feat: fastly compute service to perform assignments (FF-3552) #69
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
Changes from all commits
a813273
e985863
c8717ee
5956e65
a3f37c7
48d47aa
56e5c96
3a4b644
59cb251
a159065
6e704ef
bccce04
c338d31
ff73c47
48d2ebc
3b55f6d
3219a03
d288696
be2d5ac
9f99865
78fe3b5
f156caa
f2f27af
f1bcf3d
4ada237
8101988
50ed0ec
38f71eb
266de8a
c3c90f8
b9ca60a
26300db
26c4466
7b4eb91
95219d4
81d1f55
9622a21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,47 @@ | ||
# Make settings - @see https://tech.davis-hansson.com/p/make/ | ||
SHELL := bash | ||
.ONESHELL: | ||
.SHELLFLAGS := -eu -o pipefail -c | ||
.DELETE_ON_ERROR: | ||
MAKEFLAGS += --warn-undefined-variables | ||
MAKEFLAGS += --no-builtin-rules | ||
|
||
# Log levels | ||
DEBUG := $(shell printf "\e[2D\e[35m") | ||
INFO := $(shell printf "\e[2D\e[36m🔵 ") | ||
OK := $(shell printf "\e[2D\e[32m🟢 ") | ||
WARN := $(shell printf "\e[2D\e[33m🟡 ") | ||
ERROR := $(shell printf "\e[2D\e[31m🔴 ") | ||
END := $(shell printf "\e[0m") | ||
WASM_TARGET=wasm32-wasi | ||
FASTLY_PACKAGE=fastly-edge-assignments | ||
BUILD_DIR=target/$(WASM_TARGET)/release | ||
WASM_FILE=$(BUILD_DIR)/$(FASTLY_PACKAGE).wasm | ||
|
||
.PHONY: default | ||
default: help | ||
|
||
## help - Print help message. | ||
# Help target for easy documentation | ||
.PHONY: help | ||
help: Makefile | ||
@echo "usage: make <target>" | ||
@sed -n 's/^##//p' $< | ||
help: | ||
@echo "Available targets:" | ||
@echo " all - Default target (build workspace)" | ||
@echo " workspace-build - Build the entire workspace excluding the Fastly package" | ||
@echo " workspace-test - Test the entire workspace excluding the Fastly package" | ||
@echo " fastly-edge-assignments-build - Build only the Fastly package for WASM" | ||
@echo " fastly-edge-assignments-test - Test only the Fastly package" | ||
@echo " clean - Clean all build artifacts" | ||
|
||
.PHONY: test | ||
test: ${testDataDir} | ||
npm test | ||
|
||
# Build the entire workspace excluding the `fastly-edge-assignments` package | ||
.PHONY: workspace-build | ||
workspace-build: | ||
cargo build --workspace --exclude $(FASTLY_PACKAGE) | ||
|
||
# Run tests for the entire workspace excluding the `fastly-edge-assignments` package | ||
.PHONY: workspace-test | ||
workspace-test: | ||
cargo test --workspace --exclude $(FASTLY_PACKAGE) | ||
|
||
# Build only the `fastly-edge-assignments` package for WASM | ||
.PHONY: fastly-edge-assignments-build | ||
fastly-edge-assignments-build: | ||
rustup target add $(WASM_TARGET) | ||
cargo build --release --target $(WASM_TARGET) --package $(FASTLY_PACKAGE) | ||
|
||
# Test only the `fastly-edge-assignments` package | ||
.PHONY: fastly-edge-assignments-test | ||
fastly-edge-assignments-test: | ||
cargo test --target $(WASM_TARGET) --package $(FASTLY_PACKAGE) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use crate::ufc::{Assignment, AssignmentFormat, Environment, VariationType}; | ||
use crate::{Attributes, Configuration, Str}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::collections::HashMap; | ||
use std::sync::Arc; | ||
|
||
// Request | ||
#[derive(Debug, Deserialize)] | ||
pub struct PrecomputedAssignmentsServiceRequestBody { | ||
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. major: |
||
pub subject_key: Str, | ||
pub subject_attributes: Arc<Attributes>, | ||
// TODO: Add bandit actions | ||
// #[serde(rename = "banditActions")] | ||
// #[serde(skip_serializing_if = "Option::is_none")] | ||
// bandit_actions: Option<HashMap<String, serde_json::Value>>, | ||
} | ||
|
||
// Response | ||
#[derive(Debug, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct FlagAssignment { | ||
pub allocation_key: Str, | ||
pub variation_key: Str, | ||
pub variation_type: VariationType, | ||
pub variation_value: serde_json::Value, | ||
/// Additional user-defined logging fields for capturing extra information related to the | ||
/// assignment. | ||
#[serde(flatten)] | ||
pub extra_logging: HashMap<String, String>, | ||
pub do_log: bool, | ||
} | ||
|
||
impl FlagAssignment { | ||
pub fn try_from_assignment(assignment: Assignment) -> Option<Self> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: prefer implementing |
||
// WARNING! There is a problem here. The event is only populated for splits | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rasendubi @sameerank I believe this is a problem as described here: https://github.com/Eppo-exp/eppo-multiplatform/blob/main/eppo_core/src/ufc/compiled_flag_config.rs#L231 During compilation, an optimization step to reduce the configuration, A possible solution is to produce a configuration that is "not optimized" for use in this function so that we can access There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option is to make fields from event optional. There's no reason to include keys and extra logging if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think its a problem because we want the precomputed server to return evaluations for all flags and |
||
// that have `do_log` set to true in the wire format. This means that | ||
// all the ones present here are logged, but any splits that are not | ||
// logged are not present here. | ||
// | ||
// This is a problem for us because we want to be able to return | ||
// precomputed assignments for any split, logged or not, since we | ||
// want to be able to return them for all flags. | ||
// | ||
// We need to fix this. | ||
assignment.event.as_ref().map(|event| Self { | ||
allocation_key: event.base.allocation.clone(), | ||
variation_key: event.base.variation.clone(), | ||
variation_type: assignment.value.variation_type(), | ||
variation_value: assignment.value.variation_value(), | ||
extra_logging: event | ||
.base | ||
.extra_logging | ||
.iter() | ||
.map(|(k, v)| (k.clone(), v.clone())) | ||
.collect(), | ||
Comment on lines
+50
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as: extra_logging: event.base.extra_logging.clone(), |
||
do_log: true, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct PrecomputedAssignmentsServiceResponse { | ||
created_at: chrono::DateTime<chrono::Utc>, | ||
format: AssignmentFormat, | ||
environment: Environment, | ||
flags: HashMap<String, FlagAssignment>, | ||
} | ||
|
||
impl PrecomputedAssignmentsServiceResponse { | ||
pub fn from_configuration( | ||
configuration: Arc<Configuration>, | ||
flags: HashMap<String, FlagAssignment>, | ||
) -> Self { | ||
Self { | ||
created_at: chrono::Utc::now(), | ||
format: AssignmentFormat::Precomputed, | ||
environment: { | ||
Environment { | ||
name: configuration.flags.compiled.environment.name.clone(), | ||
} | ||
}, | ||
flags, | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ pub type Timestamp = chrono::DateTime<chrono::Utc>; | |
pub(crate) struct UniversalFlagConfigWire { | ||
/// When configuration was last updated. | ||
pub created_at: Timestamp, | ||
pub format: AssignmentFormat, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. heads up @sameerank I added a new key to UFC parsing. I see it is being returned now always so I feel it is safe to make it required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: ideally, this field should be optional because there could exist configurations without it (remember that users may be storing and passing configurations out of band now) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
/// Environment this configuration belongs to. | ||
pub environment: Environment, | ||
/// Flags configuration. | ||
|
@@ -31,6 +32,14 @@ pub(crate) struct UniversalFlagConfigWire { | |
pub bandits: HashMap<String, Vec<BanditVariationWire>>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "UPPERCASE")] | ||
pub(crate) enum AssignmentFormat { | ||
Client, | ||
Precomputed, | ||
Server, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub(crate) struct Environment { | ||
|
@@ -540,6 +549,7 @@ mod tests { | |
&r#" | ||
{ | ||
"createdAt": "2024-07-18T00:00:00Z", | ||
"format": "SERVER", | ||
"environment": {"name": "test"}, | ||
"flags": { | ||
"success": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[build] | ||
target = "wasm32-wasi" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pkg/ | ||
bin/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "fastly-edge-assignments" | ||
version = "0.1.0" | ||
edition = "2021" | ||
# Remove this line if you want to be able to publish this crate as open source on crates.io. | ||
# Otherwise, `publish = false` prevents an accidental `cargo publish` from revealing private source. | ||
publish = false | ||
|
||
[dependencies] | ||
base64-url = "2.0.0" | ||
chrono = "0.4.19" | ||
eppo_core = { version = "=4.1.1", path = "../eppo_core" } | ||
fastly = "0.11.0" | ||
serde_json = "1.0.132" | ||
serde = "1.0.192" | ||
sha2 = "0.10.0" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Eppo Assignments on Fastly Compute@Edge | ||
|
||
TODO: Add a description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# This file describes a Fastly Compute package. To learn more visit: | ||
# https://www.fastly.com/documentation/reference/compute/fastly-toml | ||
|
||
authors = ["[email protected]"] | ||
name = "Eppo Assignments on Fastly Compute@Edge" | ||
description = "Edge compute service for pre-computed Eppo flag assignments" | ||
language = "rust" | ||
manifest_version = 3 | ||
|
||
[scripts] | ||
build = "cargo build --bin fastly-edge-assignments --release --target wasm32-wasi --color always" | ||
|
||
[local_server] | ||
[local_server.kv_stores] | ||
[[local_server.kv_stores.edge-assignment-kv-store]] | ||
key = "ufc-by-sdk-key-token-hash-V--77TScV5Etm78nIMTSOdiroOh1__NsupwUwsetEVM" | ||
file = "../sdk-test-data/ufc/flags-v1.json" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this instructs the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[toolchain] | ||
channel = "stable" | ||
targets = [ "wasm32-wasi" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these changes helpful? I got tired of putting the various parameters in the right place.