xref: /aosp_15_r20/external/autotest/client/cros/cellular/hermes_utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2021 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import dbus
7import logging
8import random
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.cros.cellular import hermes_constants
12from autotest_lib.client.cros.networking import hermes_proxy
13from autotest_lib.client.cros.networking import mm1_proxy
14
15# Helper functions
16def connect_to_hermes():
17    """
18    Attempts to connect to a DBus object.
19
20    @raise: error.TestFail if connection fails.
21
22    """
23    hermes_manager = 'None'
24    try:
25        hermes_manager = \
26            hermes_proxy.HermesManagerProxy().get_hermes_manager()
27    except dbus.DBusException as e:
28        logging.error('get_hermes_manager error:%s', e)
29        raise error.TestFail('Connect to Hermes failed')
30    if hermes_manager is 'None':
31        raise error.TestFail('Could not get connection to Hermes')
32    return hermes_manager
33
34def request_installed_profiles(euicc_path, hermes_manager):
35    """
36    Check euicc at given path
37
38    @param euicc_path: path of the sim given
39    @return a dict of profiles objects. Returns None if no profile is found
40    @raise: error.TestFail if no euicc matching given path
41
42    """
43    euicc = hermes_manager.get_euicc(euicc_path)
44    if not euicc:
45        raise error.TestFail('No euicc found at:', euicc_path)
46
47    euicc.request_installed_profiles()
48    installed_profiles = euicc.get_installed_profiles()
49    if not installed_profiles:
50        logging.info('No installed profiles on euicc:%s', euicc_path)
51    return euicc, installed_profiles
52
53def install_profile(euicc_path, hermes_manager, is_prod_ci):
54    """
55    Install a profile on the euicc at euicc_path.
56
57    @param euicc_path: esim path based on testci/prodci
58    @param hermes_manager: hermes manager object
59    @param is_prod_ci: true if it is prodci test and false for testci
60    @return iccid: iccid of the installed profile or None
61
62    """
63    if not is_prod_ci:
64        is_smds_test = random.choice([True, False])
65        logging.info('is_smds_test %s', is_smds_test)
66        if is_smds_test:
67            installed_iccid = install_pending_profile_test(
68            euicc_path, hermes_manager)
69        else:
70            installed_iccid = install_profile_test(
71            euicc_path, hermes_manager)
72    else:
73        installed_iccid = get_profile(
74            euicc_path, hermes_manager, False)
75    return installed_iccid
76
77def uninstall_all_profiles(euicc_path, hermes_manager):
78    """
79    Uninstalls all installed test profiles
80
81    @param euicc_path: esim path based on testci/prodci
82    @param hermes_manager: hermes manager object
83    @raise error.TestFail if any dbus exception happens
84
85    """
86    try:
87        euicc, installed_profiles = \
88            request_installed_profiles(euicc_path, hermes_manager)
89
90        profiles_count = len(installed_profiles)
91        if profiles_count is 0: return
92
93        # also skips iccid 89010000001234567882 - R&S as its TESTING profile
94        for profile in installed_profiles.keys():
95            if ((hermes_constants.ProfileStateToString(
96                installed_profiles[profile].state) == 'INACTIVE') and
97                (hermes_constants.ProfileClassToString(
98                installed_profiles[profile].profileclass) !=
99                    'TESTING')):
100
101                logging.info('Uninstalling profile - iccid:%s',
102                            installed_profiles[profile].iccid)
103                euicc.uninstall_profile(profile)
104        logging.info('Uninstall done')
105    except dbus.DBusException as e:
106        logging.error('Failed to uninstall a profile error:%s', e)
107        raise error.TestFail('Failed to uninstall profile')
108
109
110def initialize_test(is_prod_ci_test):
111    """
112    Initialize euicc paths, connect to hermes, set test mode
113
114    @param is_prod_ci_test:  true if it is prodci test and false for testci
115
116    """
117    logging.info('===initialize_test started===')
118    mm_proxy = mm1_proxy.ModemManager1Proxy.get_proxy()
119
120    logging.info('Connect to Hermes')
121    hermes_manager = connect_to_hermes()
122
123    # Always euicc/0 is prod one and euicc/1 is for test esim profiles
124    # we prefer to hardcode euicc/0, since it acts as a check that Hermes
125    # is able to initialize without any error. If hermes encounters an
126    # error, hermes will start exposing objects such as
127    # self.prod_euicc_path = "/org/chromium/Hermes/euicc/22"
128    # self.test_euicc_path = "/org/chromium/Hermes/euicc/23"
129
130    euicc = None
131    euicc_path = None
132    for path in hermes_manager.get_available_euiccs():
133        logging.info("Found euicc at %s", path)
134        is_prod_euicc = not hermes_manager.get_euicc(path).is_test_euicc()
135        if is_prod_euicc == is_prod_ci_test:
136            euicc_path = path
137            euicc = hermes_manager.get_euicc(euicc_path)
138            break
139
140    if not euicc:
141        raise error.TestFail("Initialize test failed, " +
142                             "prod" if is_prod_ci_test else "test" +
143                             " euicc not found")
144
145    euicc.use_test_certs(not is_prod_ci_test)
146
147    if not is_prod_ci_test:
148        uninstall_all_profiles(euicc_path, hermes_manager)
149    logging.info('===initialize_test done===\n')
150    return  mm_proxy, hermes_manager, euicc_path
151
152def validate_profile_state(euicc_path, hermes_manager, iccid, is_enable):
153    """
154    Validates given profile(iccid) state
155
156    Check state of changed profile
157
158    @param euicc_path: esim path based on testci/prodci
159    @param hermes_manager: hermes manager object
160    @param iccid: iccid of the profile enabled/disabled
161    @param is_enable: true to enable profile and false to disable
162    @raise error.TestFail if any dbus exception happens
163
164    """
165    try:
166        target_state = 'ACTIVE' if is_enable else 'INACTIVE'
167        _, installed_profiles = \
168        request_installed_profiles(euicc_path, hermes_manager)
169
170        # check profile with given iccid has target_state and rest are opposite
171        for profile in installed_profiles.values():
172            # check for inactive profiles when enabled except enabled one
173            if iccid == profile.iccid:
174                if not (hermes_constants.ProfileStateToString(profile.state) ==
175                    target_state):
176                    logging.error('profile:%s not in %s state',
177                    profile.iccid, target_state)
178                    raise error.TestFail('validate_profile_state failed')
179
180        logging.info('validate_profile_state succeeded')
181    except dbus.DBusException as e:
182        logging.error('Profile %s error:%s', target_state, e)
183        raise error.TestFail('validate_profile_state failed')
184
185def set_profile_state(
186    is_active, euicc_path=None, hermes_manager=None,  iccid=None, profile=None):
187    """
188    Enable or Disable already enabled/disabled profile
189
190    @param is_active: True to enable, False to disable profile
191    @param euicc_path: esim path based on testci/prodci
192    @param hermes_manager: hermes manager object
193    @param iccid: profile iccid to enable
194    @param profile: profile object to enable/disable
195    @raise error.TestFail if expected error not resulted
196
197    """
198    logging.info('set_profile_state start')
199    if euicc_path and iccid:
200        euicc = hermes_manager.get_euicc(euicc_path)
201        profile = euicc.get_profile_from_iccid(iccid)
202
203    if is_active:
204        profile.enable()
205    else:
206        profile.disable()
207    logging.info('set_profile_state done')
208
209def get_profile_state(euicc_path, hermes_manager, iccid):
210    """
211    get profile state
212
213    @param euicc_path: esim path based on testci/prodci
214    @param hermes_manager: hermes manager object
215    @param iccid: profile iccid to find state
216    @return True if profile state is Active and False if state is Inactive
217
218    """
219    if euicc_path and iccid:
220        euicc = hermes_manager.get_euicc(euicc_path)
221        profile = euicc.get_profile_from_iccid(iccid)
222
223    return True if (hermes_constants.ProfileStateToString(profile.state) ==
224                    'ACTIVE') else False
225
226def get_profile(euicc_path, hermes_manager, is_active):
227    """
228    Returns a active/inactive profile on given euicc
229
230    This is to get already enabled or disabled profile. if not found enabled
231    profile, enable an inactive profile and if not found disable profile
232    disable an active profile
233
234    @param euicc_path: esim path based on testci/prodci
235    @param hermes_manager: hermes manager object
236    @param is_active: True to get active profile, False to get inactive profile
237    @return iccid: iccid of the active/inactive profile as requested
238    @raise error.TestFail if any dbus exception happens
239
240    """
241    try:
242        _, installed_profiles = \
243            request_installed_profiles(euicc_path, hermes_manager)
244
245        profile_found = False
246        iccid = None
247        profile_needed = 'Enabled' if is_active else 'Disabled'
248        # Find active/inactive profile
249        target_state = 'ACTIVE' if is_active else 'INACTIVE'
250
251        for profile in installed_profiles.values():
252            # skipping TESTING profiles to prevent install/uninstall operations
253            if (hermes_constants.ProfileClassToString(
254                                profile.profileclass) == 'TESTING'):
255                continue
256
257            if not (hermes_constants.ProfileStateToString(profile.state) ==
258                                target_state):
259                set_profile_state(is_active, profile=profile)
260
261            profile_found = True
262            return profile.iccid
263
264        if not profile_found:
265            logging.error('No installed profile which is %s', profile_needed)
266        return iccid
267    except dbus.DBusException as e:
268        raise error.TestFail('get_profile failed :', repr(e))
269
270def get_iccid_of_disabled_profile(euicc_path, hermes_manager, is_prod_ci):
271    """
272    Get profile with disabled status and return its iccid
273
274    For test esim install new profile and return iccid of that profile
275    For prod esim having two profiles is prerequisite, return disabled profile
276
277    @param euicc_path: esim path based on testci/prodci
278    @param hermes_manager: hermes manager object
279    @param is_prod_ci:  true if it is prodci test and false for testci
280    @return iccid: iccid of the installed profile or None
281
282    """
283    if not is_prod_ci:
284        installed_iccid = install_profile_test(euicc_path, hermes_manager)
285    else:
286        # get disabled profile on a prod esim, if not exist then do disable one
287        _, installed_profiles = \
288        request_installed_profiles(euicc_path, hermes_manager)
289        for profile in installed_profiles.values():
290            if (hermes_constants.ProfileClassToString(profile.profileclass) ==
291                    'TESTING'):
292                continue
293
294            if (hermes_constants.ProfileStateToString(profile.state) ==
295                    'INACTIVE'):
296                return profile.iccid
297
298        installed_iccid = get_profile(euicc_path, hermes_manager, False)
299
300    return installed_iccid
301
302# Test functions
303def enable_or_disable_profile_test(
304    euicc_path, hermes_manager, iccid, is_enable):
305    """
306    Validates enable/disable profile api DBus call
307
308    @param euicc_path: esim path based on testci/prodci
309    @param hermes_manager: hermes manager object
310    @param iccid: iccid of the profile to be enabled/disabled
311    @param is_enable: true to enable profile and false to disable
312    @raise error.TestFail if any dbus exception happens
313
314    """
315    try:
316        logging.info('===enable_or_disable_profile_test started===')
317        profile_action = 'Enable' if is_enable else 'Disable'
318        logging.info('%s :', profile_action)
319        euicc, installed_profiles = \
320            request_installed_profiles(euicc_path, hermes_manager)
321        # Profile objects maybe stale if IsActive is false
322        # Switch to the euicc we are interested in before
323        # performing an op.
324
325        profile_found = False
326        target_state = 'ACTIVE' if is_enable else 'INACTIVE'
327        # Find active or inactive profile to enable/disable
328        for profile in installed_profiles.values():
329            if not (hermes_constants.ProfileStateToString(profile.state) ==
330                    target_state):
331                if iccid is None or iccid == profile.iccid:
332                    logging.info('Profile to %s:%s', profile_action,
333                                profile.iccid)
334                    profile_found = True
335                    set_profile_state(is_enable, profile=profile)
336                    logging.info('===enable_or_disable_profile_test '
337                                'succeeded===\n')
338                    break
339        if not profile_found:
340            raise error.TestFail('enable_or_disable_profile_test failed -'
341                    'No profile to ' + profile_action)
342        # Check profile state
343        validate_profile_state(euicc_path, hermes_manager, iccid, is_enable)
344    except dbus.DBusException as e:
345        logging.error('Profile %s error:%s', profile_action, e)
346        raise error.TestFail('enable_or_disable_profile_test Failed')
347
348def install_profile_test(euicc_path, hermes_manager):
349    """
350    Validates InstallProfileFromActivationCode api on test euicc
351
352    use SMDS calls to find iccid, activation code from pending profiles
353    and install those profiles, this requires profiles generated based
354    on EID of test esims in lab devices
355
356    @param euicc_path: esim path based on testci/prodci
357    @param hermes_manager: hermes manager object
358    @return iccid: iccid of the installed profile or None
359    @raise error.TestFail if any dbus exception happens
360
361    """
362    try:
363        # get all pending profiles which are generated on DUT EID
364        # Read all profiles activation code from pending profile dict
365        # Install a profile from activation code, have iccid and
366        # Check the presence of this profile after installation
367
368        logging.info('===install_profile_test started===')
369        activation_code = None
370        confirmation_code = ""
371        iccid = None
372        euicc = None
373
374        euicc, installed_profiles = \
375            request_installed_profiles(euicc_path, hermes_manager)
376
377        euicc.request_pending_profiles(dbus.String('prod.smds.rsp.goog'))
378        logging.info('euicc chosen:%s', euicc_path)
379        profiles_pending = euicc.get_pending_profiles()
380        if not profiles_pending:
381            logging.error('install_profile_test: pending profile not found')
382            raise error.TestFail('No pending profile found on euicc:',
383                                 euicc_path)
384
385        profile_path_to_install, profile_to_install = \
386            list(profiles_pending.items())[0]
387        logging.debug('First pending profile:%s', profile_path_to_install)
388
389        iccid = profile_to_install.iccid
390        activation_code = profile_to_install.activationcode
391
392        logging.info('Installing iccid:%s act_code:%s conf_code:%s',
393                     iccid, activation_code, confirmation_code)
394        # Install
395        euicc.install_profile_from_activation_code(
396            activation_code, confirmation_code)
397
398        # Check if iccid found in installed profiles, installation success
399        installed_profiles = euicc.get_installed_profiles()
400
401        if ((installed_profiles[profile_path_to_install] is None) or
402            (installed_profiles[profile_path_to_install].iccid !=
403             profile_to_install.iccid)):
404            logging.error('install_profile_test failed. Test Failed.')
405            raise error.TestFail('No installed profile found on euicc:',
406                                 euicc_path)
407
408        logging.info('===install_profile_test succeeded===\n')
409        return iccid
410    except dbus.DBusException as e:
411        logging.error('Failed to install a pending profile')
412        raise error.TestFail('install_profile_test failed with ',
413                             repr(e))
414
415def install_pending_profile_test(euicc_path, hermes_manager):
416    """
417    Validates InstallPendingProfile api on test euicc
418    Find a profile from list of esim pending profiles which is not
419    installed yet and install that profile
420
421    Required to create pending profiles for each EID(euicc sim) in lab dut.
422    create profiles from stork giving lab devices EID. puts profiles in
423    pending state for that euicc when RequestPendingProfiles called.
424
425    @param euicc_path: esim path based on testci/prodci
426    @param hermes_manager: hermes manager object
427    @return iccid: iccid of the installed profile or None
428    @raise error.TestFail if any dbus exception happens
429
430    """
431    logging.info('===install_pending_profile_test started===')
432    profile_to_install = None
433
434    euicc, installed_profiles = \
435            request_installed_profiles(euicc_path, hermes_manager)
436
437    euicc.request_pending_profiles(dbus.String('prod.smds.rsp.goog'))
438    profiles_pending = euicc.get_pending_profiles()
439    if not profiles_pending:
440        logging.error(
441            'install_pending_profile_test: pending profile not found')
442        raise error.TestFail('No pending profile found on euicc:',
443                             euicc_path)
444
445    profile_path_to_install, profile_to_install = list(profiles_pending.items())[0]
446    iccid = profile_to_install.iccid
447    activation_code = profile_to_install.activationcode
448
449    logging.info('Installing profile:%s iccid:%s act_code:%s',
450                 profile_path_to_install, iccid, activation_code)
451
452    try:
453        # Install
454        profile = euicc.install_pending_profile(
455            profile_path_to_install, "")
456        logging.info('Installed pending profile is %s', profile)
457        if not profile:
458            logging.error('No profile object returned after install')
459            return None
460    except dbus.DBusException as e:
461        logging.error('Failed to install pending profile:%s', e)
462        raise error.TestFail('Failed to install pending profile',
463                                   repr(e))
464
465    # Find above installed profile, if not exists raise test failure
466    installed_profiles = euicc.get_installed_profiles()
467    if ((installed_profiles[profile_path_to_install] is None) or
468        (installed_profiles[profile_path_to_install].iccid !=
469         profile_to_install.iccid)):
470        raise error.TestFail('Install pending profile failed :',
471                             profile_path_to_install)
472
473    logging.info('===install_pending_profile_test succeeded===\n')
474    return iccid
475
476def uninstall_profile_test(euicc_path, hermes_manager, iccid):
477    """
478    Validates UninstallProfile api by uninstalling any randomly
479    selected installed profile
480
481    @param euicc_path: esim path based on testci/prodci
482    @param hermes_manager: hermes manager object
483    @param iccid: iccid of the profile to be uninstalled
484    @raise error.TestFail if any dbus exception happens
485
486    """
487    logging.info('===uninstall_profile_test started===')
488    # Getinstalled profiles list and randomly uninstall a profile
489    try:
490        euicc, installed_profiles = \
491            request_installed_profiles(euicc_path, hermes_manager)
492
493        profile_to_uninstall = euicc.get_profile_from_iccid(iccid)
494        if not profile_to_uninstall:
495            raise error.TestFail('No valid profile found at:', euicc_path)
496
497        profile_path = profile_to_uninstall.path
498        uninstalled_profile = None
499
500        # Hermes does not support uninstalling test profiles yet.
501        if hermes_constants.ProfileClassToString(
502                profile_to_uninstall.profileclass) != 'TESTING':
503            logging.info('profile to uninstall is:%s', profile_path)
504            euicc.uninstall_profile(profile_path)
505            uninstalled_profile = profile_path
506            logging.info('uninstall_profile_test succeeded')
507
508        if not uninstalled_profile:
509            raise error.TestFail(
510                'uninstall_profile_test failed - No uninstallable profile')
511
512        # Try to find the uninstalled profile, if exists raise test failure
513        profiles_installed = euicc.get_installed_profiles()
514        for profile in profiles_installed.keys():
515            if uninstalled_profile in profile:
516                raise error.TestFail('uninstall_profile_test profile Failed')
517        logging.info('===uninstall_profile_test succeeded===\n')
518    except dbus.DBusException as e:
519        raise error.TestFail('Failed to uninstall profile', e)
520