def handle(self, *args, **options):
        self._options = options
        self._verbosity = int(options.get('verbosity'))
        self._canvas_admins = CanvasAdmins()
        self._canvas_accounts = CanvasAccounts()
        self._pws = PWS()
        self._accounts = Accounts()

        self._canvas_role_mapping = {}
        for role in settings.ASTRA_ROLE_MAPPING:
            self._canvas_role_mapping[settings.ASTRA_ROLE_MAPPING[role]] = role
        if not self._options.get('commit'):
            logger.info('NOT commiting ASTRA admins.  Only logging what would change.')

        # Compare table to Canvas reality
        try:
            if self._verbosity > 0:
                logger.info('building admin table from ASTRA...')

            ASTRA({ 'verbosity': self._verbosity }).load_all_admins({ 'override': self._options.get('override_id') })

            if self._verbosity > 0:
                logger.info('building sub account list...')

            accounts = []
            root = options.get('root_account')
            root_account_id = root if self._canvas_accounts.valid_canvas_id(root) else self._canvas_accounts.sis_account_id(root)

            if self._verbosity > 1:
                logger.info('get account for id: %s...' % root_account_id)

            root_canvas_admins = []

            account = self.get_account(root_account_id)
            sub_accounts = self.get_all_sub_accounts(root_account_id)

            accounts.append(account)
            accounts.extend(sub_accounts)
            for account in accounts:
                canvas_id = account.account_id
                self._shown_canvas_id = canvas_id
                account_model = self._accounts.load_account(account)

                # reconcile admins against Admin table
                if account_model.is_sdb():
                    self._shown_id = account.sis_account_id
                else:
                    self._shown_id = 'canvas_%s' % canvas_id

                astra_admins = Admin.objects.filter(canvas_id=canvas_id)
                canvas_admins = self.get_admins(canvas_id)

                if account_model.is_root():
                    root_canvas_admins = canvas_admins

                if self._verbosity > 0 and (len(astra_admins) or len(canvas_admins)):
                    logger.info('%d ASTRA and %s Canvas admins in account %s (%s)' %
                                   (len(astra_admins), len(canvas_admins),
                                    account.name, account.account_id))

                for astra_admin in astra_admins:
                    user_role = {
                        'canvas_account_id': account.account_id,
                        'role': settings.ASTRA_ROLE_MAPPING[astra_admin.role],
                        'net_id': astra_admin.net_id
                    }

                    canvas_admin = None

                    for admin in canvas_admins:
                        if (user_role['net_id'] == admin.user.login_id and
                            user_role['role'] == admin.role):
                            canvas_admin = admin
                            canvas_admin.in_astra = True
                            break

                    if astra_admin.is_deleted:
                        if canvas_admin:
                            user_role['user_id'] = canvas_admin.user.user_id
                            self._remove_admin(**user_role)
                            try:
                                ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                                user_role['role'] = ancillary['canvas_role']
                                if ancillary['account'] == 'common':
                                    self._remove_admin(**user_role)
                                elif len(Admin.objects.filter(net_id=canvas_admin.user.login_id,
                                                              role=astra_admin.role,
                                                              is_deleted__isnull=True)) == 0:
                                    user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID
                                    self._remove_admin(**user_role)
                            except Admin.DoesNotExist:
                                pass
                            except KeyError:
                                pass

                            astra_admin.deleted_date = datetime.datetime.utcnow().replace(tzinfo=utc)
                            astra_admin.save()
                    elif not canvas_admin:
                        user_role['user_id'] = self._canvas_admins.sis_user_id(astra_admin.reg_id)
                        self._add_admin(**user_role)

                        if astra_admin.role in settings.ANCILLARY_CANVAS_ROLES:
                            ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                            user_role['role'] = ancillary['canvas_role']
                            if ancillary['account'] == 'root':
                                user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID

                            self._add_admin(**user_role)

                        astra_admin.provisioned_date = datetime.datetime.utcnow().replace(tzinfo=utc)
                        astra_admin.save()
                    else:
                        if self._verbosity > 0:
                            logger.info('  %s already in Canvas as %s' % (astra_admin.net_id, astra_admin.role))

                        if astra_admin.role in settings.ANCILLARY_CANVAS_ROLES:
                            ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                            user_role['role'] = ancillary['canvas_role']
                            add_ancillary = True
                            if ancillary['account'] == 'root':
                                user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID
                                for root_canvas_admin in root_canvas_admins:
                                    if (astra_admin.net_id == root_canvas_admin.user.login_id
                                        and root_canvas_admin.role == user_role['role']):
                                        add_ancillary = False
                                        break
                            elif  ancillary['account'] == 'common':
                                for canvas_admin in canvas_admins:
                                    if (astra_admin.net_id == canvas_admin.user.login_id
                                        and canvas_admin.role == user_role['role']):
                                        add_ancillary = False
                                        break

                            if add_ancillary:
                                user_role['user_id'] = self._canvas_admins.sis_user_id(astra_admin.reg_id)
                                self._add_admin(**user_role)
                            elif self._verbosity > 0:
                                logger.info('  %s ancillary role %s already in %s'
                                               % (user_role['net_id'],
                                                  user_role['role'],
                                                  user_role['canvas_account_id']))

                # remove unrecognized admins
                for canvas_admin in canvas_admins:
                    if (self._options['remove_non_astra']
                        and not (hasattr(canvas_admin, 'in_astra')
                                 and canvas_admin.in_astra)):
                        if self._is_ancillary(account, canvas_admin.role,
                                              canvas_admin.user.login_id, account_model.is_root()):
                            logger.info('preserving ancillary role: %s as %s'
                                           % (canvas_admin.user.login_id,
                                              canvas_admin.role))

                            continue

                        try:
                            self._remove_admin(canvas_account_id=canvas_id,
                                               net_id=canvas_admin.user.login_id,
                                               user_id=canvas_admin.user.user_id,
                                               role=canvas_admin.role)
                        except DataFailureException as err:
                            if err.args[1] == 404:
                                logger.info('Ancillary role NOT in Canvas: %s as %s'
                                               % (canvas_admin.user.login_id,
                                                  canvas_admin.role))
                            else:
                                raise

            if self._verbosity > 0:
                logger.info('Done.')

        except ASTRAException as err:
            logger.error('ASTRA ERROR: %s\nAborting.' % err)

        except DataFailureException as err:
            if err.status in self.retry_status_codes:
                logger.error('RETRIES EXCEEDED: %s\nAborting.' % err)
                Admin.objects.dequeue()
            else:
                logger.error('REST ERROR: %s\nAborting.' % err)

        self.update_job()
class Command(SISProvisionerCommand):
    help = "Reconcile ASTRA / Canvas Administrators"

    option_list = BaseCommand.option_list + (
        make_option('-r', '--root-account', action='store', dest='root_account', type="string",
                    default=settings.RESTCLIENTS_CANVAS_ACCOUNT_ID,
                    help='reconcile sections at and below root account (default: %s)'
                    % settings.RESTCLIENTS_CANVAS_ACCOUNT_ID),
        make_option('-c', '--commit', action='store_true', dest='commit',
                    default=False, help='update Canvas with ASTRA admins and roles'),
        make_option('-a', '--astra-is-authoritative', action='store_true', dest='remove_non_astra',
                    default=False, help='Remove Canvas admins not found in ASTRA'),
        make_option('-o', '--override', action='store', dest='override_id',
                    default=0, help='Override blocked Canvas admins import of given process id'),
        )

    max_retry = 5
    sleep_interval = 5
    retry_status_codes = [408, 500, 502, 503, 504]

    def handle(self, *args, **options):
        self._options = options
        self._verbosity = int(options.get('verbosity'))
        self._canvas_admins = CanvasAdmins()
        self._canvas_accounts = CanvasAccounts()
        self._pws = PWS()
        self._accounts = Accounts()

        self._canvas_role_mapping = {}
        for role in settings.ASTRA_ROLE_MAPPING:
            self._canvas_role_mapping[settings.ASTRA_ROLE_MAPPING[role]] = role
        if not self._options.get('commit'):
            logger.info('NOT commiting ASTRA admins.  Only logging what would change.')

        # Compare table to Canvas reality
        try:
            if self._verbosity > 0:
                logger.info('building admin table from ASTRA...')

            ASTRA({ 'verbosity': self._verbosity }).load_all_admins({ 'override': self._options.get('override_id') })

            if self._verbosity > 0:
                logger.info('building sub account list...')

            accounts = []
            root = options.get('root_account')
            root_account_id = root if self._canvas_accounts.valid_canvas_id(root) else self._canvas_accounts.sis_account_id(root)

            if self._verbosity > 1:
                logger.info('get account for id: %s...' % root_account_id)

            root_canvas_admins = []

            account = self.get_account(root_account_id)
            sub_accounts = self.get_all_sub_accounts(root_account_id)

            accounts.append(account)
            accounts.extend(sub_accounts)
            for account in accounts:
                canvas_id = account.account_id
                self._shown_canvas_id = canvas_id
                account_model = self._accounts.load_account(account)

                # reconcile admins against Admin table
                if account_model.is_sdb():
                    self._shown_id = account.sis_account_id
                else:
                    self._shown_id = 'canvas_%s' % canvas_id

                astra_admins = Admin.objects.filter(canvas_id=canvas_id)
                canvas_admins = self.get_admins(canvas_id)

                if account_model.is_root():
                    root_canvas_admins = canvas_admins

                if self._verbosity > 0 and (len(astra_admins) or len(canvas_admins)):
                    logger.info('%d ASTRA and %s Canvas admins in account %s (%s)' %
                                   (len(astra_admins), len(canvas_admins),
                                    account.name, account.account_id))

                for astra_admin in astra_admins:
                    user_role = {
                        'canvas_account_id': account.account_id,
                        'role': settings.ASTRA_ROLE_MAPPING[astra_admin.role],
                        'net_id': astra_admin.net_id
                    }

                    canvas_admin = None

                    for admin in canvas_admins:
                        if (user_role['net_id'] == admin.user.login_id and
                            user_role['role'] == admin.role):
                            canvas_admin = admin
                            canvas_admin.in_astra = True
                            break

                    if astra_admin.is_deleted:
                        if canvas_admin:
                            user_role['user_id'] = canvas_admin.user.user_id
                            self._remove_admin(**user_role)
                            try:
                                ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                                user_role['role'] = ancillary['canvas_role']
                                if ancillary['account'] == 'common':
                                    self._remove_admin(**user_role)
                                elif len(Admin.objects.filter(net_id=canvas_admin.user.login_id,
                                                              role=astra_admin.role,
                                                              is_deleted__isnull=True)) == 0:
                                    user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID
                                    self._remove_admin(**user_role)
                            except Admin.DoesNotExist:
                                pass
                            except KeyError:
                                pass

                            astra_admin.deleted_date = datetime.datetime.utcnow().replace(tzinfo=utc)
                            astra_admin.save()
                    elif not canvas_admin:
                        user_role['user_id'] = self._canvas_admins.sis_user_id(astra_admin.reg_id)
                        self._add_admin(**user_role)

                        if astra_admin.role in settings.ANCILLARY_CANVAS_ROLES:
                            ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                            user_role['role'] = ancillary['canvas_role']
                            if ancillary['account'] == 'root':
                                user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID

                            self._add_admin(**user_role)

                        astra_admin.provisioned_date = datetime.datetime.utcnow().replace(tzinfo=utc)
                        astra_admin.save()
                    else:
                        if self._verbosity > 0:
                            logger.info('  %s already in Canvas as %s' % (astra_admin.net_id, astra_admin.role))

                        if astra_admin.role in settings.ANCILLARY_CANVAS_ROLES:
                            ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                            user_role['role'] = ancillary['canvas_role']
                            add_ancillary = True
                            if ancillary['account'] == 'root':
                                user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID
                                for root_canvas_admin in root_canvas_admins:
                                    if (astra_admin.net_id == root_canvas_admin.user.login_id
                                        and root_canvas_admin.role == user_role['role']):
                                        add_ancillary = False
                                        break
                            elif  ancillary['account'] == 'common':
                                for canvas_admin in canvas_admins:
                                    if (astra_admin.net_id == canvas_admin.user.login_id
                                        and canvas_admin.role == user_role['role']):
                                        add_ancillary = False
                                        break

                            if add_ancillary:
                                user_role['user_id'] = self._canvas_admins.sis_user_id(astra_admin.reg_id)
                                self._add_admin(**user_role)
                            elif self._verbosity > 0:
                                logger.info('  %s ancillary role %s already in %s'
                                               % (user_role['net_id'],
                                                  user_role['role'],
                                                  user_role['canvas_account_id']))

                # remove unrecognized admins
                for canvas_admin in canvas_admins:
                    if (self._options['remove_non_astra']
                        and not (hasattr(canvas_admin, 'in_astra')
                                 and canvas_admin.in_astra)):
                        if self._is_ancillary(account, canvas_admin.role,
                                              canvas_admin.user.login_id, account_model.is_root()):
                            logger.info('preserving ancillary role: %s as %s'
                                           % (canvas_admin.user.login_id,
                                              canvas_admin.role))

                            continue

                        try:
                            self._remove_admin(canvas_account_id=canvas_id,
                                               net_id=canvas_admin.user.login_id,
                                               user_id=canvas_admin.user.user_id,
                                               role=canvas_admin.role)
                        except DataFailureException as err:
                            if err.args[1] == 404:
                                logger.info('Ancillary role NOT in Canvas: %s as %s'
                                               % (canvas_admin.user.login_id,
                                                  canvas_admin.role))
                            else:
                                raise

            if self._verbosity > 0:
                logger.info('Done.')

        except ASTRAException as err:
            logger.error('ASTRA ERROR: %s\nAborting.' % err)

        except DataFailureException as err:
            if err.status in self.retry_status_codes:
                logger.error('RETRIES EXCEEDED: %s\nAborting.' % err)
                Admin.objects.dequeue()
            else:
                logger.error('REST ERROR: %s\nAborting.' % err)

        self.update_job()

    @retry(DataFailureException, status_codes=retry_status_codes,
           tries=max_retry, delay=sleep_interval, logger=logger)
    def get_admins(self, canvas_id):
        return self._canvas_admins.get_admins(canvas_id)

    @retry(DataFailureException, status_codes=retry_status_codes,
           tries=max_retry, delay=sleep_interval, logger=logger)
    def get_account(self, root_account_id):
        return self._canvas_accounts.get_account(root_account_id)

    @retry(DataFailureException, status_codes=retry_status_codes,
           tries=max_retry, delay=sleep_interval, logger=logger)
    def get_all_sub_accounts(self, root_account_id):
        return self._canvas_accounts.get_all_sub_accounts(root_account_id)

    def _is_ancillary(self, account, canvas_role, canvas_login_id, is_root):
        ancillary = settings.ANCILLARY_CANVAS_ROLES
        for astra_role in ancillary.keys():
            if ancillary[astra_role]['canvas_role'] == canvas_role:
                if ancillary[astra_role]['account'] == 'root' and is_root:
                    if len(Admin.objects.filter(net_id=canvas_login_id,
                                                role=astra_role,
                                                is_deleted__isnull=True)) > 0:
                        return True
                elif ancillary[astra_role]['account'] == 'common':
                    try:
                        Admin.objects.get(account_id=account.account_id,
                                          net_id=canvas_login_id,
                                          role=astra_role,
                                          is_deleted__isnull=True)
                        return True
                    except Admin.DoesNotExist:
                        pass

        return False

    def _add_admin(self, **kwargs):
        prefix = 'WOULD ADD'

        if self._options.get('commit'):
            prefix = 'ADDING'
            try:
                self._canvas_admins.create_admin(
                    kwargs['canvas_account_id'],
                    kwargs['user_id'],
                    kwargs['role'])
            except DataFailureException as err:
                if err.status == 404:  # Non-personal regid?
                    prefix = "ADD FAIL: MISSING USER %s" % (kwargs['user_id'])
                else:
                    raise

        self._record('  %s: %s as %s' % (
            prefix, kwargs['net_id'], kwargs['role']))

    def _remove_admin(self, **kwargs):
        action = 'WOULD DELETE'

        if kwargs['net_id'] in getattr(settings, 'CANVAS_SERVICE_USER_ACCOUNTS', []):
            action = 'SERVICE USER'
        elif self._options.get('commit'):
            action = 'DELETING'
            try:
                self._canvas_admins.delete_admin(kwargs['canvas_account_id'],
                                                 kwargs['user_id'],
                                                 kwargs['role'])
            except DataFailureException as err:
                if err.status == 404:  # Non-personal regid?
                    action = "ALREADY DELETED"
                else:
                    raise

        self._record('  %s: %s (%s) as %s' % (action, kwargs['net_id'],
                                              kwargs['user_id'], kwargs['role']))

    def _record(self, msg):
        if self._shown_id:
            logger.info('reconciling %s (%s)' % (self._shown_id, self._shown_canvas_id))
            self._shown_id = None

        logger.info(msg)