Ejemplo n.º 1
0
def ldap_role_group_delete_all(handle):
    """
    Delete all the ldap role groups

    Args:
        handle (ImcHandle)

    Returns:
        None

    Examples:
        ldap_role_group_delete_all(handle)
    """

    api = 'ldap_role_group_delete_all'
    dn_to_group_dict = {}
    mos = []

    groups = handle.query_classid('AaaLdapRoleGroup')
    for group in groups:
        if not group.name and not group.domain:
            continue
        group.admin_action = 'clear'
        mos.append(group)
        dn_to_group_dict[group.dn] = group.name

    ret = []
    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        "Delete LDAP groups failed",
                                        ldap_role_group_callback,
                                        dn_to_group_dict)

        if len(ret) != 0:
            error_msg = 'Delete LDAP groups failed:\n'
            for item in ret:
                object = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += object + ": " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 2
0
def _process_response(response, api,  callback, dn_to_vd_dict):
    ret = process_conf_mos_response(response, api, False,
                                    'sd card config set m5',
                                    callback,
                                    dn_to_vd_dict)
    if len(ret) != 0:
        error_msg = 'cannot enable/disable virtual drive:\n'
        for item in ret:
            obj = item["Object"]
            error = item["Error"]
            error = sanitize_message(error)
            error_msg += "[virtual drive " + obj + "] " + error + "\n"

        raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret
    return results
Ejemplo n.º 3
0
def _process_response(response, api, error_msg, callback=None, *cbargs):
    callback_params = []
    if callback is not None:
        callback_params = [callback]
        callback_params.extend(cbargs)

    ret = process_conf_mos_response(response, api, False, error_msg,
                                    *callback_params)
    error_msg += "\n"
    if len(ret) != 0:
        for item in ret:
            obj = item["Object"]
            error_msg += "%s: " % obj
            error = item["Error"]
            error = sanitize_message(error)
            error_msg += error + "\n"

        raise ImcOperationErrorDetail(api, error_msg, ret)

    return ret
Ejemplo n.º 4
0
def snmp_user_add_all(handle, users=None):
    """
    Adds snmp user.

    Args:
        handle (ImcHandle)
        users (list): list of user dict
          keys:
            name (string): snmp username
            security_level (string): "authpriv", "authnopriv", "noauthnopriv"
            auth (string): "MD5", "SHA"
            auth_pwd (string): password
                for existing user
            privacy (string): "AES", "DES"
            privacy_pwd (string): privacy password
                for existing user
          example:
            [{'name': 'snmpuser',
              'security_level': 'authpriv',
              'auth': 'MD5',
              'auth_pwd': 'password',
              'privacy': 'AES',
              'privacy_pwd': 'password'}
            ]

    Returns:
        list: List of CommSnmpUser Managed Object

    Raises:
        ImcOperationError is maximum number of users already configured

    Example:
        snmp_user_add_all( handle,
                    users = [{'name': 'snmpuser',
                            'security_level': 'authpriv',
                            'auth': 'MD5', 'auth_pwd': 'password',
                            'privacy': 'AES', 'privacy_pwd': 'password'])
    """
    from imcsdk.mometa.comm.CommSnmpUser import CommSnmpUser
    from imcsdk.mometa.comm.CommSnmpUser import CommSnmpUserConsts
    from imcsdk.mometa.comm.CommSnmp import CommSnmpConsts

    api = 'snmp_user_add_all'
    parent_mo = _get_mo(handle, dn=SNMP_DN)
    if parent_mo.admin_state != CommSnmpConsts.ADMIN_STATE_ENABLED:
        raise ImcOperationError(api, 'SNMP is not enabled.')

    dn_to_user_dict = {}
    mos = []
    id = 0
    for user in users:
        name = user.pop('name', None)
        security_level = user.pop('security_level', None)
        _validate_api_prop('name', name, api)
        _validate_api_prop('security_level', security_level, api)

        auth = user.pop('auth', None)
        auth_pwd = user.pop('auth_pwd', None)
        privacy = user.pop('privacy', None)
        privacy_pwd = user.pop('privacy_pwd', None)

        params = {
            'name': name,
            'security_level': security_level
            }

        if security_level == CommSnmpUserConsts.SECURITY_LEVEL_AUTHNOPRIV:
            #_validate_api_prop('auth', auth, api, True, ['MD5', 'SHA'])
            _validate_api_prop('auth_pwd', auth_pwd, api)
            params['auth'] = auth
            params['auth_pwd'] = auth_pwd
        elif security_level == CommSnmpUserConsts.SECURITY_LEVEL_AUTHPRIV:
            _validate_api_prop('auth', auth, api, True, ['MD5', 'SHA'])
            _validate_api_prop('auth_pwd', auth_pwd, api)
            _validate_api_prop('privacy', privacy, api, True, ['AES', 'DES'])
            _validate_api_prop('privacy_pwd', privacy_pwd, api)
            params['auth'] = auth
            params['auth_pwd'] = auth_pwd
            params['privacy'] = privacy
            params['privacy_pwd'] = privacy_pwd

        id += 1
        mo = CommSnmpUser(parent_mo_or_dn=parent_mo, id=str(id))
        mo.set_prop_multiple(**params)
        mos.append(mo)
        dn_to_user_dict[mo.dn] = mo.name

    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        'Create SNMP users failed',
                                        snmp_users_callback,
                                        dn_to_user_dict)
        if len(ret) != 0:
            error_msg = 'Create/Update SNMP users failed:\n'
            for item in ret:
                obj = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += "[User " + obj + "] " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 5
0
def snmp_trap_add_all(handle, traps=None):
    """
    Adds snmp trap.

    Args:
        handle (ImcHandle)
        traps (list): list of trap dict
          keys:
            hostname (string): ip address
            admin_state (string): enabled, disabled
            version (string): "v2c", "v3"
            notification_type (string): "informs", "traps"
                Required only for version "v2c" and "v3"
            user (string): send traps for a specific user
            port (int): port

    Returns:
        list: List of CommSnmpTrap Managed Object

    Example:
        snmp_trap_add_all(handle,
                          traps=[{hostname: "10.10.10.10",
                                 port: "162",
                                 version:"v2c",
                                 notification_type:"informs"}]
                         )
    """
    from imcsdk.mometa.comm.CommSnmp import CommSnmpConsts
    from imcsdk.mometa.comm.CommSnmpTrap import CommSnmpTrap
    from imcsdk.mometa.comm.CommSnmpTrap import CommSnmpTrapConsts

    api = 'snmp_trap_add_all'
    parent_mo = _get_mo(handle, dn=SNMP_DN)
    if parent_mo.admin_state != CommSnmpConsts.ADMIN_STATE_ENABLED:
        raise ImcOperationError(api, 'SNMP is not enabled.')

    dn_to_trap_dict = {}
    mos = []
    id = 0
    for trap in traps:
        hostname = trap.pop('hostname', None)
        _validate_api_prop('hostname', hostname, api)

        version = trap.pop('version', None)
        _validate_api_prop('version', version, api, True,
                           [CommSnmpTrapConsts.VERSION_V1,
                            CommSnmpTrapConsts.VERSION_V2C,
                            CommSnmpTrapConsts.VERSION_V3])

        notification_type = trap.pop('notification_type', None)
        _validate_api_prop('notification_type', notification_type, api,
                           True,
                           [CommSnmpTrapConsts.NOTIFICATION_TYPE_INFORMS,
                            CommSnmpTrapConsts.NOTIFICATION_TYPE_TRAPS])

        admin_state = trap.pop('admin_state', 'enabled')
        _validate_api_prop('admin_state', admin_state, api, True,
                           [CommSnmpTrapConsts.ADMIN_STATE_ENABLED,
                            CommSnmpTrapConsts.ADMIN_STATE_DISABLED])

        user = trap.pop('user', None)
        port = trap.pop('port', None)

        if version == CommSnmpTrapConsts.VERSION_V2C and user:
            user = None
        if version == CommSnmpTrapConsts.VERSION_V3:
            notification_type = CommSnmpTrapConsts.NOTIFICATION_TYPE_TRAPS

        params = {
            'hostname': hostname,
            'version': version,
            'notification_type': notification_type,
            'admin_state': admin_state,
            'port': str(port) if port else None,
            'user': user
            }

        id += 1
        mo = CommSnmpTrap(parent_mo_or_dn=parent_mo, id=str(id))
        mo.set_prop_multiple(**params)
        mos.append(mo)
        dn_to_trap_dict[mo.dn] = mo.hostname

    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        'Create SNMP traps failed',
                                        snmp_traps_callback,
                                        dn_to_trap_dict)
        if len(ret) != 0:
            error_msg = 'Create/Update SNMP traps failed:\n'
            for item in ret:
                obj = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += "[Trap " + obj + "] " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 6
0
def vmedia_mount_create_all(handle, mappings = None, server_id=1, timeout=60):
    """
        This method will make one request to create all the vmedia mappings
        Args:
            handle (ImcHandle)
            mappings (list): list of mappings dict
              keys:
                volume_name (string): Name of the volume or identity of the image
                map (string): "cifs", "nfs", "www"
                mount_options (string): Options to be passed while mounting the image
                remote_share (string): URI of the image
                remote_file (string): name of the image
                username (string): username
                password (string): password
            server_id (int): Server Id to be specified for C3260 platforms

        Returns:
            List of CommVMediaMap object

        Examples:
            vmedia_mount_create_all(
                handle,
                mappings=[{volume_name: "A",
                map: "www"
                mount_options: "ro"
                remote_share: "http://10.10.10.20/test/"
                remote_file: "a.iso"
                username: ""
                password: ""}]
            )
        """

    api = 'vmedia_mount_create_all'
    mos = []
    dn_to_vmedia_dict = {}

    for mapping in mappings:

        volume_name  = mapping.get('volume_name')
        map          = mapping.get('map')
        remote_share = mapping.get('remote_share')
        remote_file  = mapping.get('remote_file')
        _validate_api_prop('volume_name', volume_name, api)
        _validate_api_prop('map',map, api)
        _validate_api_prop('remote_share', remote_share, api)
        _validate_api_prop('remote_file', remote_file,api)
        params = {
            'map':         map,
            'remote_file':  remote_file,
            'remote_share': remote_share
        }
        if mapping.get('mount_options'):
            params['mount_options'] = mapping.get('mount_options')
        if map != CommVMediaMapConsts.MAP_NFS:
            mount_options = mapping.get('mount_options')

            #In CIMC, security context authentication protocol for CIFS Share, is always set to "ntlm" by default.
            #If authentication protocol is set to none, CIMC rejects the mapping and the deployment fails.
            #Hence, removing the security context from parameters mount_options if the authentication protocol is set to none.

            mount_options_array = mount_options.split(",")
            for option in mount_options_array:
                if "sec" in option and len(option.split("=")) >= 2 and option.split("=")[1] == "none":
                    mount_options_array.remove(option)
            if len(mount_options_array) == 0:
                del params['mount_options']
            else:
                new_mount_options = ','.join([str(element) for element in mount_options_array])
                params['mount_options'] = new_mount_options
            if mapping.get('username'):
                params['username'] = mapping.get('username')
            if mapping.get('password'):
                params['password'] = mapping.get('password')

        mo = CommVMediaMap(parent_mo_or_dn=_get_vmedia_mo_dn(handle, server_id),
                           volume_name=volume_name)
        mo.set_prop_multiple(**params)
        mos.append(mo)
        dn_to_vmedia_dict[mo.dn] = mo.volume_name

    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False, 'Create Virtual Media mapping failed',vmedia_mounts_callback,
                                               dn_to_vmedia_dict)
        if len(ret) != 0:
            error_msg = 'Create Virtual Media mapping failed:\n'
            for item in ret:
                obj = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += "[Virtual Media mapping " + obj + "] " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    mapping_error_msg = ''
    timeout_error_msg = ''
    for mo in mos:
        wait_time = 0
        interval = 10
        while wait_time < timeout:
            mapping_mo = handle.query_dn(mo.dn)
            if mapping_mo:
                existing_mapping_status = mapping_mo.mapping_status
                if existing_mapping_status.lower() == "ok":
                    break
                elif re.match(r"error", existing_mapping_status.lower()):
                    mapping_error_msg += "[Virtual Media mapping "+ mo.volume_name + "] " +existing_mapping_status + "\n"
                    break

            time.sleep(interval)
            wait_time += interval

        if wait_time >= timeout:
            timeout_error_msg += "[Virtual Media mapping "+ mo.volume_name +"] \n"

    if len(mapping_error_msg) != 0:
        raise ImcOperationErrorDetail(api,"Create Virtual Media mapping failed: "+ mapping_error_msg,[])

    if len(timeout_error_msg) != 0:
        raise ImcOperationErrorDetail(api,"Create Virtual Media mapping timed out: "+ timeout_error_msg,[])

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 7
0
def local_users_update(handle, users=None):
    """
    This method will create, modify or delete local users.
    It could also be a combination of these operations.

    Args:
        handle (ImcHandle)
        users (list): list of user dict
          keys:
            name (string): username
            priv (string): "admin", "user", "read-only"
            pwd (string): password
            account_status(string): "active", "inactive"
            change_password(boolean): flag used to change password
          example:
            [{'name':'dummy',
              'pwd': '*****',
              'priv': 'admin',
              'change_password': true,
              'account_status': 'active'}]

    Returns:
        boolean: flag that indicates if users were created, modified or deleted. It could also be a combination of these operations.

    Raises:
        IMCOperationError for various failure scenarios. A sample IMC Exception looks something like this:
        "Update Local Users failed, error: User:dum1 - [ErrorCode]: 2003[ErrorDescription]: Operation failed. Matching old password(s), please enter a different password.;
    Note: This error msg format is being used in Cisco Intersight to map error messages to respective users. Please excercise caution before changing it in the API.
    """

    from imcsdk.mometa.aaa.AaaUser import AaaUser
    from imcsdk.imccoreutils import sanitize_message
    api = "Update Local Users"
    if users is None:
        users = []
    if len(users) > MAX_USERS:
        raise ImcOperationError(
            api, "Number of users exceeded max allowed limit on IMC")
    update_users = False
    create_users = False
    endpoint_users = _get_local_users(handle)
    used_ids, delete_users = _delete_users(handle, users, endpoint_users)
    all_ids = range(2, MAX_USERS + 1)
    free_ids = list(set(all_ids) - set(used_ids))
    create_mos = []
    modify_mos = []
    dn_to_user_dict = {}
    aaa_user_prefix = "sys/user-ext/user-"
    id = 0
    for user in users:
        if 'name' not in user:
            raise ImcOperationError(api, "User Name is invalid")
        if 'pwd' not in user:
            raise ImcOperationError(api, "Password is invalid")
        if 'priv' not in user:
            raise ImcOperationError(api, "Privilege is invalid")
        if 'account_status' not in user:
            account_status = "active"
        else:
            account_status = user['account_status']
        if 'change_password' not in user:
            change_password = False
        else:
            change_password = user['change_password']
        name = user['name']
        pwd = user['pwd']
        priv = user['priv']
        args = {
            "name": name,
            "pwd": pwd,
            "priv": priv,
            "account_status": account_status
        }

        # Existing users are not touched and hence we can safely check the
        # endpoint users list if there is
        found_user = None
        l = [x for x in endpoint_users if x.name == name]
        if len(l) != 0:
            found_user = l[0]
        if found_user:
            if not change_password:
                args.pop('pwd', None)
            if not found_user.check_prop_match(**args):
                update_users = True
            dn_to_user_dict[aaa_user_prefix + str(found_user.id)] = name
            found_user.set_prop_multiple(**args)
            modify_mos.append(found_user)
            continue
        if len(free_ids) == 0 or id >= len(free_ids):
            raise ImcOperationError(
                api, "Cannot configure more users than allowed limit on IMC")
        create_users = True
        free_id = free_ids[id]
        dn_to_user_dict[aaa_user_prefix + str(free_id)] = name
        mo = AaaUser(parent_mo_or_dn="sys/user-ext", id=str(free_id))
        mo.set_prop_multiple(**args)
        create_mos.append(mo)
        id += 1
    ret = []
    mos = []

    mos.extend(modify_mos)
    mos.extend(create_mos)

    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        'Create/Update local users failed',
                                        user_mos_callback, dn_to_user_dict)
        if len(ret) != 0:
            error_msg = 'Create/Update local users failed:\n'
            for item in ret:
                user = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += user + ": " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    # print(create_users, update_users, delete_users)
    results["changed"] = create_users or update_users or delete_users
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 8
0
def ldap_role_group_create_all(handle, groups=None):
    """
    Creates an LDAP role groups.
    Note: This will overwrite the exisiting role groups.

    Args:
        handle (ImcHandle)
        groups (list of LDAP group dict)
            keys:
            domain (str): The LDAP server domain the group resides in.
            name (str): The name of the group in the LDAP server database.
            role (str): The role assigned to all users in this LDAP server
                        group.
                        ['read-only', 'user', 'admin']
            example:
             [{'domain': 'abcd.pqrs.com',
               'name': 'abcd',
               'role': 'user'}
             ]

    Returns:
        List of AaaLdapRoleGroup object

    Examples:
        ldap_role_group_create_all(handle,
                                   groups= [
                                    {'domain': 'abcd.pqrs.com',
                                     'name': 'abcd',
                                     'role': 'user'}])
    """
    api = 'ldap_role_group_create_all'

    dn_to_group_dict = {}
    domain_name_str_list = []
    mos = []
    id = 0

    if len(groups) > 28:
        raise ImcOperationError(api, "Maximum allowed LDAP groups are 28.")

    for group in groups:
        domain = group.get('domain', None)
        name = group.get('name', None)
        role = group.get('role', 'read-only')

        domain = domain.strip() if domain else None
        name = name.strip() if name else None
        role = role.strip() if role else None

        _validate_api_prop('domain', domain, api)
        _validate_api_prop('name', name, api)
        _validate_api_prop('role', role, api,
                           True, ['read-only', 'user', 'admin'])

        domain_name_str = domain + "_" + name
        if domain_name_str in domain_name_str_list:
            raise ImcOperationError(
                api,
                "LDAP Role Group with domain:%s name:%s already exists." % (
                    domain, name))
        domain_name_str_list.append(domain_name_str)

        params = {
            'domain': domain,
            'name': name,
            'role': role
        }

        id += 1
        mo = AaaLdapRoleGroup(parent_mo_or_dn=LDAP_DN, id=str(id))
        mo.set_prop_multiple(**params)
        mos.append(mo)

        dn_to_group_dict[mo.dn] = mo.name

    ret = []
    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        "Create LDAP groups failed",
                                        ldap_role_group_callback,
                                        dn_to_group_dict)

        if len(ret) != 0:
            error_msg = 'Create LDAP groups failed:\n'
            for item in ret:
                object = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += object + ": " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 9
0
def _apply_config_card_action_util(handle, mos_dict, vds):
    from imcsdk.mometa.storage.StorageFlexFlashVirtualDrive import \
        StorageFlexFlashVirtualDriveConsts
    # slot = _choose_slot(mos_dict['flexflash_pds'])

    # Utility drive should always be created on:
    # "slot-2",  if both cards present
    # slot in which card is present, if only one card present
    # error, if no cards present
    pds = mos_dict['flexflash_pds']
    primary_slot = None
    available_slots = _get_available_slots(pds)
    available_slots_cnt = len(available_slots)
    if available_slots_cnt == 0:
        raise ImcOperationError("Cannot set virtual drive",
                                "Cards are missing in both the slots")
    elif available_slots_cnt == 1:
        primary_slot = available_slots[0]
    elif available_slots_cnt == 2:
        primary_slot = Slot.SLOT_2

    partition_name = None
    non_util_partition_name = None
    if 'USER' in vds and hasattr(vds['USER'], 'vd_name'):
        partition_name = vds['USER'].vd_name
    if 'OS' in vds and hasattr(vds['OS'], 'vd_name'):
        non_util_partition_name = vds['OS'].vd_name
    flexflash_controller_mode_util_set(
        handle,
        card_slot=primary_slot,
        partition_name=partition_name,
        non_util_partition_name=non_util_partition_name
    )

    dn_to_vd_dict = {}
    api = "sd_card_virtual_drive_set"
    mos = []
    controller_dn = mos_dict['flexflash_controller'].dn
    for vd_type, vd in vds.items():
        if not vd.vd_enable:
            continue
        # skip OS if only one slot is available in utility mode in M4
        if vd_type == "OS" and available_slots_cnt == 1:
            continue
        partition_id = vd_map_type_id_util_m4[vd.vd_type]
        mo = _set_admin_action_flash_vd(
            handle,
            partition_id,
            StorageFlexFlashVirtualDriveConsts.ADMIN_ACTION_ENABLE_VD,
            controller_dn
        )
        mos.append(mo)
        dn_to_vd_dict[mo.dn] = vd.vd_type

    if len(mos) == 0:
        return

    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        'Configuring virtual drives failed',
                                        util_mode_cb,
                                        dn_to_vd_dict)
        if len(ret) != 0:
            error_msg = 'Configuring virtual drives failed:\n'
            for item in ret:
                obj = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += "[Virtual drive " + obj + "] " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    results["changed"] = True
    results["msg"] = ""
    results["msg_params"] = ret

    return results
Ejemplo n.º 10
0
def local_users_update(handle, users=None):
    """
    This method will create, modify or delete local users.
    It could also be a combination of these operations.

    Args:
        handle (ImcHandle)
        users (list): list of user dict
          keys:
            name (string): username
            priv (string): "admin", "user", "read-only"
            pwd (string): password
            account_status(string): "active", "inactive"
            change_password(boolean): flag used to change password
          example:
            [{'name':'dummy',
              'pwd': '*****',
              'priv': 'admin',
              'change_password': true,
              'account_status': 'active'}]

    Returns:
        boolean: flag that indicates if users were created, modified or deleted. It could also be a combination of these operations.

    Raises:
        IMCOperationError for various failure scenarios. A sample IMC Exception looks something like this:
        "Update Local Users failed, error: User:dum1 - [ErrorCode]: 2003[ErrorDescription]: Operation failed. Matching old password(s), please enter a different password.;
    """

    from imcsdk.mometa.aaa.AaaUser import AaaUser
    from imcsdk.imccoreutils import sanitize_message
    api = "Update Local Users"
    if users is None:
        raise ImcOperationError(api, "Users are invalid")
    if len(users) > MAX_USERS:
        raise ImcOperationError(api, "Number of users exceeded max allowed limit on IMC")
    update_users = False
    create_users = False
    endpoint_users = _get_local_users(handle)
    used_ids, delete_users = _delete_users(handle, users, endpoint_users)
    all_ids= range(2, MAX_USERS + 1)
    free_ids = list(set(all_ids) - set(used_ids))
    create_mos = []
    modify_mos = []
    dn_to_user_dict = {}
    aaa_user_prefix = "sys/user-ext/user-"
    id = 0
    for user in users:
        if 'name' not in user:
            raise ImcOperationError(api, "User Name is invalid")
        if 'pwd' not in user:
            raise ImcOperationError(api, "Password is invalid")
        if 'priv' not in user:
            raise ImcOperationError(api, "Privilege is invalid")
        if 'account_status' not in user:
            account_status = "active"
        else:
            account_status = user['account_status']
        if 'change_password' not in user:
            change_password = False
        else:
            change_password = user['change_password']
        name = user['name']
        pwd  = user['pwd']
        priv = user['priv']
        args = {"name": name,
                "pwd": pwd,
                "priv": priv,
                "account_status": account_status}

        # Existing users are not touched and hence we can safely check the
        # endpoint users list if there is
        found_user = None
        l = [x for x in endpoint_users if x.name == name]
        if len(l) != 0:
            found_user = l[0]
        if found_user:
            if not change_password:
                args.pop('pwd', None)
            if not found_user.check_prop_match(**args):
                update_users = True
            dn_to_user_dict[aaa_user_prefix+str(found_user.id)] = name
            found_user.set_prop_multiple(**args)
            modify_mos.append(found_user)
            continue
        if len(free_ids) == 0 or id >= len(free_ids):
            raise ImcOperationError(api,"Cannot configure more users than allowed limit on IMC")
        create_users = True
        free_id = free_ids[id]
        dn_to_user_dict[aaa_user_prefix+str(free_id)] = name
        mo = AaaUser(parent_mo_or_dn="sys/user-ext", id=str(free_id))
        mo.set_prop_multiple(**args)
        create_mos.append(mo)
        id += 1
    ret = []
    mos = []

    mos.extend(modify_mos)
    mos.extend(create_mos)

    response = handle.set_mos(mos)
    if response:
        ret = process_conf_mos_response(response, api, False,
                                        'Create/Update local users failed',
                                        user_mos_callback,
                                        dn_to_user_dict)
        if len(ret) != 0:
            error_msg = 'Create/Update local users failed:\n'
            for item in ret:
                user = item["Object"]
                error = item["Error"]
                error = sanitize_message(error)
                error_msg += user + ": " + error + "\n"

            raise ImcOperationErrorDetail(api, error_msg, ret)

    results = {}
    # print(create_users, update_users, delete_users)
    results["changed"] = create_users or update_users or delete_users
    results["msg"] = ""
    results["msg_params"] = ret

    return results