Skip to content

Conversation

@jiasli
Copy link
Member

@jiasli jiasli commented Jan 17, 2020

History Notes:

[Profile] Preview: Add new attributes homeTenantId and managedByTenants to subscription accounts. Please re-run az login for the changes to take effect
[Profile] az login: Show a warning when a subscription is listed from more than one tenants and default to the first one. To select a specific tenant when accessing this subscription, please include --tenant in az login


Update on 2020-02-04

Rename:

subscriptionTenantId    > homeTenantId
subscription_tenant_id  > home_tenant_id
_SUBSCRIPTION_TENANT_ID > _HOME_TENANT_ID

This PR does these tasks:

  1. Bump resource subscription client to API version 2019-06-01
  2. Display homeTenantId and managedByTenants for subscription accounts while not changing the fields of bare tenant accounts generated by specifying --allow-no-subscriptions
  3. Show a warning when a subscription is listed from more than one tenants
  4. Add corresponding tests for both 2016-06-01 API and 2019-06-01 API

To test:

Run az login which will show a warning when a subscription is listed from more than one tenants:

Subscription 9535 'Visual Studio Enterprise' can be accessed from tenants 72f9(default) and 2b8e. To select a specific tenant when accessing this subscription, please include --tenant in 'az login'.

In the response homeTenantId and managedByTenants are added. homeTenantId maps to tenantId of Subscriptions - List REST API (GUID is trimmed for simplicity):

{
    "cloudName": "AzureCloud",
    "id": "9535",
    "isDefault": false,
    "managedByTenants": [
        {
        "tenantId": "2b8e"
        },
        {
        "tenantId": "d6ad"
        }
    ],
    "name": "Visual Studio Enterprise",
    "state": "Disabled",
    "homeTenantId": "72f9",          // maps to tenantId from REST API
    "tenantId": "72f9",              // this is the tenant ID of the token
    "user": {
        "name": "@microsoft.com",
        "type": "user"
}

Then login with az login -t 2b8e, this subscription is now as below. Notice that tenantId has changed to reflect the token tenant ID.

{
    "cloudName": "AzureCloud",
    "id": "9535",
    "isDefault": false,
    "managedByTenants": [
        {
        "tenantId": "2b8e"
        }
    ],
    "name": "Visual Studio Enterprise",
    "state": "Disabled",
    "homeTenantId": "72f9",
    "tenantId": "2b8e",              // tenant ID is switched to the one specified by -t
    "user": {
        "name": "@microsoft.com",
        "type": "user"
    }
}

This allows the user to operate on subscription 9535 from tenant 2b8e.

For backward compatibility, switch profile with az cloud set -n AzureCloud --profile 2019-03-01-hybrid and login again az login -t 2b8e

{
    "cloudName": "AzureCloud",
    "id": "9535",
    "isDefault": false,
    "name": "Visual Studio Enterprise",
    "state": "Disabled",
    "tenantId": "2b8e",
    "user": {
        "name": "@microsoft.com",
        "type": "user"
    }
}

This time, everything is the same as before. To switch cloud back, run az cloud set -n AzureCloud --profile latest

}
# for Subscriptions - List REST API 2019-06-01's subscription account
if subscription_dict[_SUBSCRIPTION_NAME] != _TENANT_LEVEL_ACCOUNT_NAME:
if hasattr(s, 'subscription_tenant_id'):
Copy link
Member

Choose a reason for hiding this comment

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

what is the difference between subscription_tenant_id and tenant_id?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

and what's difference between subscription_tenant_id and managed_by_tenants? in the pr comments example, managed_by_tenants is related with tenant_id. az login -t is switching tenant_id. subscription_tenant_id is readonly field?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I also think these concepts are very confusing. I am following up with Lighthouse and our PMs trying to persuade them to change subscriptionTenantId to homeTenantId which is a read-only field indicating where the subscription is hosted. managedByTenants is a mapping from REST API's managedByTenants.

if subscription_dict[_SUBSCRIPTION_NAME] != _TENANT_LEVEL_ACCOUNT_NAME:
if hasattr(s, 'subscription_tenant_id'):
subscription_dict[_SUBSCRIPTION_TENANT_ID] = s.subscription_tenant_id
if hasattr(s, 'managed_by_tenants'):
Copy link
Member

@qianwens qianwens Jan 19, 2020

Choose a reason for hiding this comment

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

The code seems a little complicated here because of the check like if hasattr(s, 'managed_by_tenants'), is it possible that managed_by_tenants attribute does not exist? Can we move code like subscription_dict[_SUBSCRIPTION_TENANT_ID] = s.subscription_tenant_id to the initialization code of subscription_dict = {...} to make code more clean?

Copy link
Member Author

Choose a reason for hiding this comment

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

managed_by_tenants is only present since API version 2019-06-01. It is not possible to do conditional initialization according to python syntax. https://stackoverflow.com/a/14263905

from adal import AdalError
from azure.mgmt.resource.subscriptions.models import \
(SubscriptionState, Subscription, SubscriptionPolicies, SpendingLimit)
(SubscriptionState, Subscription, SubscriptionPolicies, SpendingLimit, ManagedByTenant)
Copy link
Contributor

Choose a reason for hiding this comment

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

Because subscription models have api contraint, please use get_models() here.

Copy link
Member Author

Choose a reason for hiding this comment

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

This test is only for latest. For specific test, it is in test_profile_v2016_06_01.py.

add_sub = True
for sub_to_compare in all_subscriptions:
if sub_to_add.subscription_id == sub_to_compare.subscription_id:
logger.warning("Subscription %s '%s' can be accessed from tenants %s(default) and %s. "
Copy link
Member

Choose a reason for hiding this comment

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

For these new output formats, do we have some spec or context for this new multi-tenant behavior? I ask this because I want to learn if we should only output one subscription while it exists multi tenants or if we should output all of them and mark one of them as default one.

Copy link
Member Author

Choose a reason for hiding this comment

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

We SHOULD and WILL display them all. But CLI is currently keying on subscriptionId in the account list which makes showing all of them impossible. This requires an overhaul of the account persistence mechanism.

@Juliehzl
Copy link
Contributor

In addition, changing api version will influence many other commands. Please make sure all the tests pass and re-record related test files.

qianwens
qianwens previously approved these changes Jan 19, 2020
@jiasli
Copy link
Member Author

jiasli commented Jan 19, 2020

@Juliehzl, thanks for the notification. I was planning to do that but don't have time yet.

@jiasli jiasli mentioned this pull request Jan 20, 2020
2 tasks
# Conflicts:
#	src/azure-cli/azure/cli/command_modules/appconfig/tests/latest/export_features_prop.json
@jiasli
Copy link
Member Author

jiasli commented Jan 21, 2020

This test failure

2020-01-21T05:00:05.1483290Z FAIL: test_dls_file_mgmt (azure.cli.command_modules.dls.tests.latest.test_dls_commands.DataLakeStoreFileScenarioTest)
2020-01-21T05:00:05.1484511Z ----------------------------------------------------------------------
2020-01-21T05:00:05.1491248Z Traceback (most recent call last):
2020-01-21T05:00:05.1492359Z   File "/opt/hostedtoolcache/Python/3.8.0/x64/lib/python3.8/site-packages/azure_devtools/scenario_tests/preparers.py", line 50, in _preparer_wrapper
2020-01-21T05:00:05.1493019Z     fn(test_class_instance, **kwargs)
2020-01-21T05:00:05.1494298Z   File "/opt/hostedtoolcache/Python/3.8.0/x64/lib/python3.8/site-packages/azure/cli/command_modules/dls/tests/latest/test_dls_commands.py", line 285, in test_dls_file_mgmt
2020-01-21T05:00:05.1495634Z     self.cmd('dls fs list -n {dls} --path "{folder1}"', checks=[
2020-01-21T05:00:05.1496572Z   File "/opt/hostedtoolcache/Python/3.8.0/x64/lib/python3.8/site-packages/azure/cli/testsdk/base.py", line 161, in cmd
2020-01-21T05:00:05.1497635Z     return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
2020-01-21T05:00:05.1499168Z   File "/opt/hostedtoolcache/Python/3.8.0/x64/lib/python3.8/site-packages/azure/cli/testsdk/base.py", line 201, in __init__
2020-01-21T05:00:05.1499518Z     self._in_process_execute(cli_ctx, command, expect_failure=expect_failure)
2020-01-21T05:00:05.1499997Z   File "/opt/hostedtoolcache/Python/3.8.0/x64/lib/python3.8/site-packages/azure/cli/testsdk/base.py", line 257, in _in_process_execute
2020-01-21T05:00:05.1500189Z     raise AssertionError(ex)
2020-01-21T05:00:05.1500731Z AssertionError: Can't overwrite existing cassette ('/opt/hostedtoolcache/Python/3.8.0/x64/lib/python3.8/site-packages/azure/cli/command_modules/dls/tests/latest/recordings/test_dls_file_mgmt.yaml') in your current record mode ('once').
2020-01-21T05:00:05.1501286Z No match for the request (<Request (GET) https://cliadls000002.azuredatalakestore.net/webhdfs/v1/?OP=LISTSTATUS&api-version=2018-09-01&listSize=4000>) was found.
2020-01-21T05:00:05.1501550Z Found 5 similar requests with 1 different matcher(s) :
...
2020-01-21T05:00:05.1509663Z 5 - (<Request (GET) https://cliadls000002.azuredatalakestore.net/webhdfs/v1/.?OP=LISTSTATUS&api-version=2018-09-01&listSize=4000>).
2020-01-21T05:00:05.1510350Z Matchers succeeded : ['method', 'scheme', 'host', 'port', '_custom_request_query_matcher']
2020-01-21T05:00:05.1510574Z Matchers failed :
2020-01-21T05:00:05.1510938Z path - assertion failure :
2020-01-21T05:00:05.1511149Z /webhdfs/v1/ != /webhdfs/v1/.

is caused by urllib3's breaking change urllib3/urllib3#1647 at util/url.py#L253-L255.

urllib3 1.24 keeps the trailing dot ., so the url /webhdfs/v1/. generated by dls is not modified. However the CI is using the latest urllib3==1.25.7 which trims the trailing . and fails due to the mismatch.

We need to record with at least urllib3==1.25.7. To update urllib3, run

pip install -U urllib3

# Conflicts:
#	src/azure-cli/azure/cli/command_modules/appconfig/tests/latest/export_features.json
@jiasli
Copy link
Member Author

jiasli commented Jan 21, 2020

For botservice, the recorded YAML files are manually edited to replace 2016-06-01 with 2019-06-01 due to the issue discussed at #8384 (comment).

@jiasli
Copy link
Member Author

jiasli commented Feb 3, 2020

Per discussion with Lighthouse team, closing as subscriptionTenantId will be changed to homeTenantId.

@jiasli jiasli closed this Feb 3, 2020
# Conflicts:
#	src/azure-cli/azure/cli/command_modules/botservice/tests/latest/recordings/test_botservice_create_should_remove_invalid_char_from_name_when_registration.yaml
#	src/azure-cli/azure/cli/command_modules/botservice/tests/latest/recordings/test_botservice_create_should_remove_invalid_char_from_name_when_webapp.yaml
@jiasli jiasli requested a review from yonzhan February 4, 2020 07:42
@jiasli jiasli requested review from arrownj and jsntcy February 6, 2020 05:38
})
}
# for Subscriptions - List REST API 2019-06-01's subscription account
if subscription_dict[_SUBSCRIPTION_NAME] != _TENANT_LEVEL_ACCOUNT_NAME:
Copy link
Member

Choose a reason for hiding this comment

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

_TENANT_LEVEL_ACCOUNT_NAME [](start = 56, length = 26)

If a subscription is a tenant level account, does it mean it only belong to one tenant? In this case, the rest api response does not has homeTenentId? why do we need this check here?

Copy link
Member Author

Choose a reason for hiding this comment

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

If a subscription is a tenant level account, does it mean it only belong to one tenant?

In that case, the account is a tenant with no subscriptions, not "belong to". The naming of subscription_dict is inaccurate. It contains both subscription and tenant accounts.

In this case, the rest api response does not has homeTenentId?

This tenant account is not generated from REST, but manually built up:

def _build_tenant_level_accounts(self, tenants):
result = []
for t in tenants:
s = self._new_account()
s.id = '/subscriptions/' + t
s.subscription = t
s.tenant_id = t
s.display_name = _TENANT_LEVEL_ACCOUNT_NAME
result.append(s)
return result

Why do we need this check here?

It doesn't make sense to include home_tenant_id and managed_by_tenants (whichs are subscription account's attributes) for a tenant account.

In fact, this is all due to the faulty original design that mixes subscription and tenant in the same list. This will and must be changed in the future to reflect the hierarchy. Tenant is a one-level-higher concept than subscription.

@qianwens qianwens dismissed their stale review February 6, 2020 12:50

revoking review

@arrownj
Copy link
Contributor

arrownj commented Feb 11, 2020

Hi @jiasli , all the logic implement here is to set homeTenantTd and managedByTenants value. I'm wondering, where do we use these properties? Just used to show to end user ?

sub_to_add.subscription_id, sub_to_add.display_name,
sub_to_compare.tenant_id, sub_to_add.tenant_id)
add_sub = False
break
Copy link
Contributor

@arrownj arrownj Feb 11, 2020

Choose a reason for hiding this comment

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

if there are n subscriptions belong to multi tenants, we will write n lines of such similar warning here ? Can we just use a common message to combine these similar messages ?

# Conflicts:
#	src/azure-cli/azure/cli/command_modules/vm/tests/latest/recordings/test_vm_create_existing_ids_options.yaml
#	src/azure-cli/azure/cli/command_modules/vm/tests/latest/recordings/test_vm_create_existing_options.yaml
#	src/azure-cli/azure/cli/command_modules/vm/tests/latest/recordings/test_vmss_create_existing_ids_options.yaml
#	src/azure-cli/azure/cli/command_modules/vm/tests/latest/recordings/test_vmss_create_existing_options.yaml
@qwordy
Copy link
Member

qwordy commented Feb 11, 2020

There are conflicting files

Copy link
Contributor

@Juliehzl Juliehzl left a comment

Choose a reason for hiding this comment

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

No more concern for me if all the tests pass.

subscriptions = self._find_using_specific_tenant(
tenant_id,
temp_credentials[_ACCESS_TOKEN])
all_subscriptions.extend(subscriptions)
Copy link
Member

Choose a reason for hiding this comment

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

When are all_subscriptions different between old behavior and your new logic? when a subscription exists in multiple tenants? for example, it exists in two tenants, will it only show once in all_subscriptions?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. A subscription will only appear once since we are keying by subscriptionId. The old behavior is newly listed ones overwriting previously listed ones, while the new behavior is newly listed ones being discarded. Thus, we can make sure subscriptions from user's home tenant is preferred (home tenant is listed earlier).

Copy link
Member

@jsntcy jsntcy left a comment

Choose a reason for hiding this comment

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

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants