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

Allocation Id #37840

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6db8216
Adding Telemetry
mrm9084 Apr 5, 2024
acc62d9
Telemetry Support
mrm9084 Apr 15, 2024
c333311
fixing formatting
mrm9084 Apr 17, 2024
2893693
Update _azureappconfigurationprovider.py
mrm9084 Apr 24, 2024
6ade779
Update _azureappconfigurationproviderasync.py
mrm9084 Apr 24, 2024
3ec51ec
formatting
mrm9084 Apr 24, 2024
d1c5a17
changing doc style due to pylint-next
mrm9084 Apr 24, 2024
5595657
fixing kwargs docs
mrm9084 Apr 24, 2024
246d259
Formatting
mrm9084 Apr 25, 2024
07c771a
Review comments
mrm9084 Apr 29, 2024
26f5042
Changed label checking.
mrm9084 Apr 29, 2024
5d485ab
black format changes
mrm9084 Apr 29, 2024
c09247b
pylint
mrm9084 Apr 29, 2024
958761e
Update sdk/appconfiguration/azure-appconfiguration-provider/azure/app…
mrm9084 Apr 30, 2024
fac284f
added space checks
mrm9084 Apr 30, 2024
50755b0
Update conftest.py
mrm9084 May 10, 2024
ce64aca
Merge remote-tracking branch 'upstream/main' into TelemetrySupport
mrm9084 Jul 2, 2024
c727b1c
Merge branch 'main' into TelemetrySupport
mrm9084 Aug 19, 2024
33ae94e
moved telemetry to client wrapper
mrm9084 Aug 19, 2024
c8f3b40
fixing format
mrm9084 Aug 19, 2024
79c9053
Merge branch 'main' into TelemetrySupport
mrm9084 Aug 28, 2024
d9cd086
Merge remote-tracking branch 'upstream/main' into TelemetrySupport
mrm9084 Sep 6, 2024
0456c6d
updating after merge
mrm9084 Sep 6, 2024
411dd2e
Merge remote-tracking branch 'upstream/main' into TelemetrySupport
mrm9084 Sep 9, 2024
7e33ecb
fixing black issue
mrm9084 Sep 9, 2024
126937a
removing unused imports
mrm9084 Sep 9, 2024
3261601
AllocationId
mrm9084 Sep 12, 2024
963c0f4
Update CODEOWNERS
mrm9084 Sep 12, 2024
58cccc2
Merge branch 'main' into AllocationId
mrm9084 Sep 12, 2024
775df1a
Update CODEOWNERS
mrm9084 Sep 12, 2024
e5f6ed4
fixing issues
mrm9084 Sep 12, 2024
0f688d2
Merge branch 'AllocationId' of https://github.com/mrm9084/azure-sdk-f…
mrm9084 Sep 12, 2024
b73b002
Update _client_manager_base.py
mrm9084 Sep 12, 2024
923362f
Fixing configuration value empty in calc
mrm9084 Sep 12, 2024
274c372
fixing pylint
mrm9084 Sep 12, 2024
c4dd229
Merge branch 'main' into AllocationId
mrm9084 Sep 13, 2024
6421788
Update _constants.py
mrm9084 Sep 13, 2024
59b745c
Merge remote-tracking branch 'upstream/main' into AllocationId
mrm9084 Oct 4, 2024
185389d
review comments
mrm9084 Oct 11, 2024
e36cc7d
fixing allocation check
mrm9084 Oct 11, 2024
50fe5fe
format fix
mrm9084 Oct 11, 2024
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
# ServiceLabel: %App Configuration Provider

# PRLabel: %App Configuration Provider
/sdk/appconfiguration/azure-appconfiguration-provider/ @mametcal @albertofori @avanigupta @mrm9084
/sdk/appconfiguration/azure-appconfiguration-provider/ @albertofori @avanigupta @mrm9084 @rossgrambo

# ServiceLabel: %Attestation
# PRLabel: %Attestation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -------------------------------------------------------------------------
import json
import time
import random
import hashlib
import base64
from dataclasses import dataclass
from typing import Dict, List
from typing import Dict, List, Optional, Mapping, Any
from azure.appconfiguration import ( # type:ignore # pylint:disable=no-name-in-module
FeatureFlagConfigurationSetting,
)
Expand All @@ -25,24 +26,104 @@
ETAG_KEY,
FEATURE_FLAG_REFERENCE_KEY,
FEATURE_FLAG_ID_KEY,
ALLOCATION_ID_KEY,
)

FALLBACK_CLIENT_REFRESH_EXPIRED_INTERVAL = 3600 # 1 hour in seconds
MINIMAL_CLIENT_REFRESH_INTERVAL = 30 # 30 seconds

JSON = Mapping[str, Any]


@dataclass
class _ConfigurationClientWrapperBase:
endpoint: str

@staticmethod
def _generate_allocation_id(feature_flag_value: Dict[str, JSON]) -> Optional[str]:
"""
Generates an allocation ID for the specified feature.
seed=123abc\ndefault_when_enabled=Control\npercentiles=0,Control,20;20,Test,100\nvariants=Control,standard;Test,special # pylint:disable=line-too-long

:param Dict[str, JSON] feature_flag_value: The feature to generate an allocation ID for.
:rtype: str
:return: The allocation ID.
"""

allocation_id = ""
allocated_variants = []

allocation: Optional[JSON] = feature_flag_value.get("allocation")

if allocation:
# Seed
allocation_id = f"seed={allocation.get('seed', '')}"

# DefaultWhenEnabled
if "default_when_enabled" in allocation:
allocated_variants.append(allocation.get("default_when_enabled"))

allocation_id += f"\ndefault_when_enabled={allocation.get('default_when_enabled', '')}"
Copy link

@zhiyuanliang-ms zhiyuanliang-ms Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't default_when_enabled base64 encoded here (just like what you did for other variants in percentile allocation)

Is this by design?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a value that would cause a collision here- but for consistency I wouldn't be opposed to adding it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# Percentile
allocation_id += "\npercentiles="

percentile = allocation.get("percentile")

if percentile:
percentile_allocations = sorted(
(x for x in percentile if x.get("from") != x.get("to")),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we validate that from, to and variant fields are all not None?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From logic in the creation of percentile allocation to must be an int so it will raise an error on creation if it isn't. from is optional[int], so we have to check it.

key=lambda x: x.get("from"),
)

for percentile_allocation in percentile_allocations:
if "variant" in percentile_allocation:
allocated_variants.append(percentile_allocation.get("variant"))

allocation_id += ";".join(
f"{pa.get('from')}," f"{base64.b64encode(pa.get('variant').encode()).decode()}," f"{pa.get('to')}"
for pa in percentile_allocations
)
else:
allocation_id = "seed=\ndefault_when_enabled=\npercentiles="
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved

if not allocated_variants and (not allocation or not allocation.get("seed")):
return None

# Variants
allocation_id += "\nvariants="

variants_value = feature_flag_value.get("variants")
if variants_value and (isinstance(variants_value, list) or all(isinstance(v, dict) for v in variants_value)):
if allocated_variants:
if isinstance(variants_value, list) and all(isinstance(v, dict) for v in variants_value):
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved
sorted_variants: List[Dict[str, Any]] = sorted(
(v for v in variants_value if v.get("name") in allocated_variants),
key=lambda v: v.get("name"),
)

for v in sorted_variants:
allocation_id += f"{base64.b64encode(v.get('name', '').encode()).decode()},"
if "configuration_value" in v:
allocation_id += f"{json.dumps(v.get('configuration_value', ''), separators=(',', ':'))}"
allocation_id += ";"
allocation_id = allocation_id[:-1]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the sorted_variant list is empty? The last character of allocation_id won't ";"


# Create a sha256 hash of the allocation_id
hash_object = hashlib.sha256(allocation_id.encode())
hash_digest = hash_object.digest()

# Encode the first 15 bytes in base64 url
allocation_id_hash = base64.urlsafe_b64encode(hash_digest[:15]).decode()
return allocation_id_hash

@staticmethod
def _calculate_feature_id(key, label):
basic_value = f"{key}\n"
if label and not label.isspace():
basic_value += f"{label}"
feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest()
encoded_flag = base64.b64encode(feature_flag_id_hash_bytes)
encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_")
encoded_flag = base64.urlsafe_b64encode(feature_flag_id_hash_bytes)
return encoded_flag[: encoded_flag.find(b"=")]

def _feature_flag_telemetry(
Expand All @@ -58,10 +139,14 @@ def _feature_flag_telemetry(
feature_flag_reference = f"{endpoint}kv/{feature_flag.key}"
if feature_flag.label and not feature_flag.label.isspace():
feature_flag_reference += f"?label={feature_flag.label}"
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id(
feature_flag.key, feature_flag.label
)
if feature_flag_value[TELEMETRY_KEY].get("enabled"):
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id(
feature_flag.key, feature_flag.label
)
allocation_id = self._generate_allocation_id(feature_flag_value)
if allocation_id:
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ALLOCATION_ID_KEY] = allocation_id

def _feature_flag_appconfig_telemetry(
self, feature_flag: FeatureFlagConfigurationSetting, filters_used: Dict[str, bool]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@

TELEMETRY_KEY = "telemetry"
METADATA_KEY = "metadata"
ETAG_KEY = "ETag"

ALLOCATION_ID_KEY = "AllocationId"
ETAG_KEY = "ETag"
FEATURE_FLAG_REFERENCE_KEY = "FeatureFlagReference"
FEATURE_FLAG_ID_KEY = "FeatureFlagId"

PERCENTAGE_FILTER_NAMES = ["Percentage", "PercentageFilter", "Microsoft.Percentage", "Microsoft.PercentageFilter"]
TIME_WINDOW_FILTER_NAMES = ["TimeWindow", "TimeWindowFilter", "Microsoft.TimeWindow", "Microsoft.TimeWindowFilter"]
TARGETING_FILTER_NAMES = ["Targeting", "TargetingFilter", "Microsoft.Targeting", "Microsoft.TargetingFilter"]
Expand Down