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

[BUG] Hashmap to Map Cast Exception in TargetingFilter.java when using Application Configuration #35823

Closed
noah-msft-brabec opened this issue Jul 11, 2023 · 28 comments
Labels
azure-spring All azure-spring related issues Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-triage Workflow: This issue needs the team to triage. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that

Comments

@noah-msft-brabec
Copy link

noah-msft-brabec commented Jul 11, 2023

Describe the bug
When checking if a feature flag is enabled the exception java.lang.ClassCastException is thrown.

Exception or Stack Trace

Caused by: java.lang.ClassCastException: java.util.HashMap$Values cannot be cast to java.util.Map
	at com.azure.spring.cloud.feature.management.filters.TargetingFilter.evaluate(TargetingFilter.java:134) ~[spring-cloud-azure-feature-management-4.8.0.jar:4.8.0]
	at com.azure.spring.cloud.feature.management.FeatureManager.isFeatureOn(FeatureManager.java:114) ~[spring-cloud-azure-feature-management-4.8.0.jar:4.8.0]
	at com.azure.spring.cloud.feature.management.FeatureManager.lambda$checkFeature$2(FeatureManager.java:106) ~[spring-cloud-azure-feature-management-4.8.0.jar:4.8.0]
	at java.util.stream.MatchOps$1MatchSink.accept(MatchOps.java:90) ~[?:1.8.0_332]
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) ~[?:1.8.0_332]
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) ~[?:1.8.0_332]
	at java.util.HashMap$ValueSpliterator.tryAdvance(HashMap.java:1673) ~[?:1.8.0_332]
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[?:1.8.0_332]
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[?:1.8.0_332]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[?:1.8.0_332]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[?:1.8.0_332]
	at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:230) ~[?:1.8.0_332]
	at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:196) ~[?:1.8.0_332]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:1.8.0_332]
	at java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:516) ~[?:1.8.0_332]
	at com.azure.spring.cloud.feature.management.FeatureManager.checkFeature(FeatureManager.java:106) ~[spring-cloud-azure-feature-management-4.8.0.jar:4.8.0]
	at com.azure.spring.cloud.feature.management.FeatureManager.isEnabledAsync(FeatureManager.java:63) ~[spring-cloud-azure-feature-management-4.8.0.jar:4.8.0]
	at our java code

To Reproduce
Setup an http request or a piece of code that attempts to check if a feature flag is enabled. Make sure the feature flag has a feature filter with the default percentage set to 0% and an allowed group that is set to 100%. Running this code will cause the cast exception to fire.

Code Snippet
Here is my Java code that initiates the call to check if a flag is enabled:
Boolean.TRUE.equals(featureManager.isEnabledAsync("someFeatureFlag").block())

This is the decompiled code that is throwing the exception from TargetingFilter.java:

Map<String, Map<String, Object>> exclusionMap = (Map<String, Map<String, Object>>) parameters
    .get(EXCLUSION);
Map<String, Object> exclusionAsLists = new HashMap<>();
if (exclusionMap != null) {
    exclusionAsLists.put(USERS, exclusionMap.getOrDefault(USERS, new HashMap<String, Object>()).values());
    exclusionAsLists.put(GROUPS, exclusionMap.getOrDefault(GROUPS, new HashMap<String, Object>()).values());
}

The error is specifically thrown on this line when the default branch is taken:
exclusionAsLists.put(USERS, exclusionMap.getOrDefault(USERS, new HashMap<String, Object>()).values());

Expected behavior
No cast exception is thrown, and that the feature flag check returns either a true or false.

Screenshots
Here is the state of the debugger right before the exception is thrown:
Screenshot 2023-07-11 at 10 32 55 AM

Setup (please complete the following information):

  • OS: MacOS 13.4.1 (22F82)
  • IDE: IntelliJ
  • Library/Libraries: spring-cloud-azure-feature-management-4.8.0.jar
  • Java version: 8
  • App Server/Environment: Tomcat
  • Frameworks: Spring Boot 2.7.12

If you suspect a dependency version mismatch (e.g. you see NoClassDefFoundError, NoSuchMethodError or similar), please check out Troubleshoot dependency version conflict article first. If it doesn't provide solution for the problem, please provide:

  • verbose dependency tree (mvn dependency:tree -Dverbose)
  • exception message, full stack trace, and any available logs

Additional context
I am using a remote configuration store. My local app is talking with a config store in the portal. It connects via a connection string.

Here are the related configured properties:

spring.cloud.azure.appconfiguration.enabled=true
spring.cloud.azure.appconfiguration.stores[0].feature-flags.enabled=true
spring.cloud.azure.appconfiguration.stores[0].connection-string=<Connection string redacted>
spring.cloud.azure.appconfiguration.stores[0].enabled=true
spring.cloud.azure.appconfiguration.stores[0].monitoring.enabled=true
spring.cloud.azure.appconfiguration.stores[0].monitoring.refresh-interval=30s
spring.cloud.azure.appconfiguration.stores[0].monitoring.triggers[0].key=sentinel
spring.cloud.azure.appconfiguration.stores[0].monitoring.feature-flag-refresh-interval=30s
spring.cloud.azure.appconfiguration.stores[0].feature-flags.selects[0].label-filter=,${spring.profiles.active}

The config store has one key/value called sentinel that allows for dynamic refresh and one feature flag with a feature filter. Here is a screenshot of the details. The value past Tenant: is a guid:
Screenshot 2023-07-11 at 10 41 15 AM

Information Checklist
Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

  • [-] Bug Description Added
  • [-] Repro Steps Added
  • [-] Setup information Added
@github-actions github-actions bot added azure-spring All azure-spring related issues Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-triage Workflow: This issue needs the team to triage. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that labels Jul 11, 2023
@noah-msft-brabec noah-msft-brabec changed the title [BUG] [App Configuration] Hashmap to Map Cast Exception in TargetingFilter.java [BUG] Hashmap to Map Cast Exception in TargetingFilter.java when using Application Configuration Jul 11, 2023
@noah-msft-brabec
Copy link
Author

This appears to only be affecting us when we use a group or user override in a feature filter. If we have a simple feature flag that is only checking if the flag is on or off the check works.

@noah-msft-brabec
Copy link
Author

I've been doing a little more testing. So far I've only faced this bug when using the feature manager directly. The same casting error does not occur when using the @FeatureGate method annotation.

@noah-msft-brabec
Copy link
Author

noah-msft-brabec commented Jul 17, 2023

I've been chatting with an internal resource and they directed me to the following workaround:

  1. Downgrade the Azure Application Configuration libraries to version 4.7.0
  2. In the advanced edit screen of your feature flag, delete the entry named Exclusion if one exists.

These two steps allowed the feature check to work for me again.

@mrm9084
Copy link
Member

mrm9084 commented Jul 18, 2023

Reverting to 4.7.0 should fix this for now if you are running into this issue. PR is up for a fix for this #35920.

@mrm9084
Copy link
Member

mrm9084 commented Jul 19, 2023

4.9.1 was released and fixes this issue.

@mrm9084 mrm9084 closed this as completed Jul 19, 2023
@tejas097
Copy link

tejas097 commented Aug 1, 2023

@mrm9084

I am recieving following error

java.lang.ClassCastException: class java.util.HashMap$Values cannot be cast to class java.util.Map (java.util.HashMap$Values and java.util.Map are in module java.base of loader 'bootstrap')
at com.azure.spring.cloud.feature.management.filters.TargetingFilter.evaluate(TargetingFilter.java:132) ~[spring-cloud-azure-feature-management-5.3.0.jar:5.3.0]
at com.azure.spring.cloud.feature.management.FeatureManager.isFeatureOn(FeatureManager.java:114) ~[spring-cloud-azure-feature-management-5.3.0.jar:5.3.0]
at com.azure.spring.cloud.feature.management.FeatureManager.lambda$checkFeature$2(FeatureManager.java:106) ~[spring-cloud-azure-feature-management-5.3.0.jar:5.3.0]
at java.base/java.util.stream.MatchOps$1MatchSink.accept(MatchOps.java:90) ~[na:na]
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
at java.base/java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1856) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
at java.base/java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:230) ~[na:na]
at java.base/java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:196) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:632) ~[na:na]
at com.azure.spring.cloud.feature.management.FeatureManager.checkFeature(FeatureManager.java:106) ~[spring-cloud-azure-feature-management-5.3.0.jar:5.3.0]
at com.azure.spring.cloud.feature.management.FeatureManager.isEnabledAsync(FeatureManager.java:63) ~[spring-cloud-azure-feature-management-5.3.0.jar:5.3.0]
at com.cvs.specialty.controller.Feature.feature(Feature.java:27) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)

I am using com.azure.spring:spring-cloud-azure-feature-management-web:5.3.0.

and I am using spring boot 3.0.1

@component
public class TargetingContextAccessorImpl implements TargetingContextAccessor {
@OverRide
public void configureTargetingContext(TargetingContext targetingContext) {
targetingContext.setUserId(" XXXX ");
}
}

This is targetContext configuration I am using

I am creating targeting filter bean

@bean("Microsoft.Targeting")
public TargetingFilter targetingFilter(TargetingContextAccessor contextAccessor) {
return new TargetingFilter(contextAccessor, new TargetingEvaluationOptions().setIgnoreCase(true));
}

And just calling featureManager.isEnabledAsync("myfeature").block()

it's giving above error when I try to execute featureManager.isEnabledAsync("myfeature").block()

can you please help me resolve this? @mrm9084

@mrm9084
Copy link
Member

mrm9084 commented Aug 1, 2023

The patch was never released for the 5.x versions. A new release should be out soon with the fix. @saragluna, we need to make sure this gets out. I saw the new 4.x versions going out yesterday.

@tejas097
Copy link

tejas097 commented Aug 2, 2023

@mrm9084

Targeting group filtering is also not working as expected.

{
"id": "myfeature",
"description": "",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "Microsoft.Targeting",
"parameters": {
"Audience": {
"Users": [],
"Groups": [
{
"Name": "abc",
"RolloutPercentage": 100
}
],
"DefaultRolloutPercentage": 0
}
}
}
]
}
}

This is my feature configuration in azure app config.

public class TargetingContextAccessorImpl implements TargetingContextAccessor {

String userId = "xyz";

@Override
    public void configureTargetingContext(TargetingContext targetingContext) {
    System.out.println(userId);
    targetingContext.setUserId(userId);
    ArrayList<String> groups = new ArrayList<String>();
    groups.add("abc");
    targetingContext.setGroups(groups);
    }

}

Here xyz is not part of group abc then How come feature is enabled for xyz.

can you please address this issue? @mrm9084

@mrm9084
Copy link
Member

mrm9084 commented Aug 2, 2023

String userId = "xyz";

@OverRide
public void configureTargetingContext(TargetingContext targetingContext) {
System.out.println(userId);
targetingContext.setUserId(userId);
ArrayList groups = new ArrayList();
groups.add("abc");
targetingContext.setGroups(groups);
}

In the example you gave, everyone will be part of the group "abc"? So it should alway return true?

@tejas097
Copy link

tejas097 commented Aug 2, 2023

String userId = "xyz";
@OverRide
public void configureTargetingContext(TargetingContext targetingContext) {
System.out.println(userId);
targetingContext.setUserId(userId);
ArrayList groups = new ArrayList();
groups.add("abc");
targetingContext.setGroups(groups);
}

In the example you gave, everyone will be part of the group "abc"? So it should alway return true?

But xyz user is not part of abc group then why it's true for him?

@mrm9084
Copy link
Member

mrm9084 commented Aug 2, 2023

Am I missing something. You also hardcoded the userId to always be "xyz"?

@tejas097
Copy link

tejas097 commented Aug 2, 2023

Am I missing something. You also hardcoded the userId to always be "xyz"?

@component
@requestScope
public class TargetingContextAccessorImpl implements TargetingContextAccessor {

String userId = "xyz";
@Override
    public void configureTargetingContext(TargetingContext targetingContext) {
    System.out.println(userId);
    targetingContext.setUserId(userId);

// ArrayList groups = new ArrayList();
// groups.add("abc");
// targetingContext.setGroups(groups);
}
}

Now I have added xyz in abc group and feature configuration is still same

{
"id": "myfeature",
"description": "",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "Microsoft.Targeting",
"parameters": {
"Audience": {
"Users": [],
"Groups": [
{
"Name": "abc",
"RolloutPercentage": 100
}
],
"DefaultRolloutPercentage": 0
}
}
}
]
}
}

I am recieving not enabled now ?? @mrm9084

@tejas097
Copy link

tejas097 commented Aug 2, 2023

@mrm9084 How featuremanagement sdk will verify if user is part of group or not ?

@mrm9084
Copy link
Member

mrm9084 commented Aug 2, 2023

TargetingContextAccessor is how you provide us with the user/group info of the current active user, or whatever you are targeting. You set it to be part of the @RequestScope so it will have access to the current request. I assume somewhere you have auth on the user that you can set to be the group info.

@mrm9084
Copy link
Member

mrm9084 commented Aug 2, 2023

This is a simple example as a POC, you should be able to replace HttpServletRequest with your auth.

@Component
@RequestScope
public class TargetingContextImpl implements TargetingContextAccessor {

    @Autowired
    private HttpServletRequest request;

    @Override
    public void configureTargetingContext(TargetingContext context) {
        context.setUserId(request.getParameter("user"));

        List<String> groups = new ArrayList<>();

        groups.add(request.getParameter("group"));

        context.setGroups(groups);
    }

}

@tejas097
Copy link

tejas097 commented Aug 2, 2023

So for groups we have to rely on AAD Token then right!! App configuration doesn't check the users group by itself dynamically right ? @mrm9084

@mrm9084
Copy link
Member

mrm9084 commented Aug 2, 2023

That is correct. We didn't want to force any one direction. If you look at the dependencies of the library, it doesn't take dependencies on any azure service, not even App Configuration.

@tejas097
Copy link

tejas097 commented Aug 2, 2023

@mrm9084

Here is my feature configuration

{
"id": "myfeature",
"description": "",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "Microsoft.Targeting",
"parameters": {
"Audience": {
"Users": [],
"Groups": [
{
"Name": "abc",
"RolloutPercentage": 50
}
],
"DefaultRolloutPercentage": 0
}
}
}
]
}
}

Here how will the Rollout percentage work for abc group. I have 3 user xyz, efg and pqr in group abc. how will feature management lib will pick 50% rollout for these users. I am recieving flag disable for all users.

Here is my context code

@requestScope
public class TargetingContextAccessorImpl implements TargetingContextAccessor {

@Autowired
HttpServletRequest request;
@Override
    public void configureTargetingContext(TargetingContext targetingContext) {
    ArrayList<String> groups = new ArrayList<String>();
    groups.add("abc");
    targetingContext.setGroups(groups);
    }

}

@mrm9084
Copy link
Member

mrm9084 commented Aug 2, 2023

@tejas097. First, You don't have three users in the group. Everyone single request is part of the group "abc" as you have hardcoded the group to always be abc.

Second, you aren't setting a userid, so there is no difference between any two users.

Third, this is a statistical 50% of users will be enabled/disabled. You need a large sample size to actually have it be 50%. We use the combination of UserId/Group/FeatureName to create a unique hash to put users into the different percentages.

@tejas097
Copy link

tejas097 commented Aug 3, 2023

@mrm9084

How will I have to create bean for Microsoft.TimeWindow it says bean is missing when I enable time window frame. And also how to create percentage filter bean in spring boot as I am not able to find any proper documentation for that!!

can you please help @mrm9084

@mrm9084
Copy link
Member

mrm9084 commented Aug 4, 2023

@tejas097, we have created the filters, but they are not enabled by default. You can do this by adding them to your @Configuration

@tejas097
Copy link

tejas097 commented Aug 4, 2023

@tejas097, we have created the filters, but they are not enabled by default. You can do this by adding them to your @Configuration

@mrm9084 do we have to create percentage filter bean under Microsoft.Targetting ? or what should be the bean name for default rollout percentage like Microsoft.Targeting or Microsoft.TimeWindow something like that?

@mrm9084
Copy link
Member

mrm9084 commented Aug 4, 2023

The bean name should match the name of the feature filter. For example:

    @Bean(name = "Microsoft.TimeWindow")
    public TimeWindowFilter timeWindowFilter() {
        return new TimeWindowFilter();
    }

    @Bean(name = "Microsoft.Targeting")
    @Scope("request")
    public TargetingFilter targettingFilter(TargetingContextImpl context) {
        return new TargetingFilter(context, new TargetingEvaluationOptions().setIgnoreCase(true));
    }

@tejas097
Copy link

tejas097 commented Aug 4, 2023

@mrm9084 percentage filter bean also part of Microsoft.Targeting or I will have to add some other bean name for percentage filter ?

@mrm9084
Copy link
Member

mrm9084 commented Aug 4, 2023

@tejas097, the name should follow the same format. Percentage filter isn't one provided by default in the App Configuration services anywhere, it still is in the provider though. The name should match the name you put for the filter in the feature flag. For example:

{
	"id": "Random",
	"description": "",
	"enabled": true,
	"conditions": {
		"client_filters": [
			{
				"name": "percentageFilter",
				"parameters": {
					"Value": 50
				}
			}
		]
	}
}

In this case it would be percentageFilter.

Note: This filter is more of an example filter than anything else as it is pure random, generating a random number every request between 0 and 100 and returning true if the result is less than or equal to the Value field.

@tejas097
Copy link

tejas097 commented Aug 4, 2023

{
				"name": "percentageFilter",
				"parameters": {
					"Value": 50
				}
			}

How percentage filter is different from default percentage rollout percentage which app configuration provides under targeting filter ?

@mrm9084
Copy link
Member

mrm9084 commented Aug 4, 2023

If you run this for example 50 percent of the time it will be yes/no. It doesn't mater who/what is going on. It is just a 50/50 chance.

For Targeting, if you have default percentage rollout set to 50, a statistical 50 percent of users will get true, the other 50 percent will get false. And they will always get those values.

Another nice thing about Targeting is that if you change it from 50 to 60, all of the people that had true, will continue to get true. Just another 10% will get true also.

@tejas097
Copy link

tejas097 commented Aug 4, 2023

@mrm9084 can azure sdk for feature management allow us to update feature flag if it has permission to do it ? and also can this sdk for feature management can give feature details what we have currently like what filter it has and other information regarding feature filter ?

@github-actions github-actions bot locked and limited conversation to collaborators Nov 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
azure-spring All azure-spring related issues Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-triage Workflow: This issue needs the team to triage. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that
Projects
Archived in project
Development

No branches or pull requests

3 participants