def converge_chef(self, state_data, api,
                      node_id, **kwargs):  # pragma: no cover
        def safe_get_fact(node, fact):
            if not fact in node['facts']:
                return None

            return node['facts'][fact]

        def verify_facts(node, facts):
            for fact in facts:
                if not fact in node['facts']:
                    return False

            return True

        # we are converging a node.  If the node is a container,
        # that probably implies converging all nodes under it.
        self.logger.info('Converging node %s via chef-client backend' % (
            node_id,))

        node = api._model_get_by_id('nodes', node_id)
        api.apply_expression(node_id, 'attrs.converged := false')

        if not 'chef_server_consumed' in node['facts']:
            return self._fail(msg='missing fact: chef_server_consumed')

        chef_server_consumed = node['facts']['chef_server_consumed']
        cs = api._model_get_by_id('nodes', chef_server_consumed)

        if not cs:
            return self._fail(msg='cannot find consumed chef server')

        if not verify_facts(cs, ['chef_server_uri',
                                 'chef_server_client_name',
                                 'chef_server_client_pem']):
            return self._fail(msg='chef server missing chef attrs')

        chef_server_uri = cs['facts']['chef_server_uri']
        chef_server_client_name = cs['facts']['chef_server_client_name']
        chef_server_client_pem = cs['facts']['chef_server_client_pem']

        self.logger.debug('Creating connection to chef server')

        # make a chef api object
        pem = StringIO.StringIO(chef_server_client_pem)
        rsa_key = chef.rsa.Key(pem)

        chef_api = chef.ChefAPI(chef_server_uri,
                                rsa_key,
                                chef_server_client_name)
        pem.close()

        if not 'chef_environment' in node['facts']:
            # this node has been pulled out of a chef environment.
            # this is hateful, but...
            api.apply_expression(node_id, 'facts.backends := '
                                 'remove(facts.backends, "chef-client")')

            if self._node_exists(node['name'], chef_api):
                chef_node = chef.Node(node['name'], chef_api)
                chef_node.delete()

            return self._ok()

        # generate node and environment settings
        node_attrs, env_attrs = self._represent_node_attributes(api, node_id)

        self.logger.debug('node: %s' % node_attrs)
        self.logger.debug('environment: %s' % env_attrs)

        nova_role = node['facts']['nova_role']
        chef_environment = node['facts']['chef_environment'].replace(' ', '_')

        # create the environment if it does not exist
        env = None

        if not self._environment_exists(chef_environment, chef_api):
            self.logger.debug('Creating non-existent environment: %s' %
                              chef_environment)

            env = chef.Environment.create(chef_environment,
                                          api=chef_api)
            env.save()
        else:
            env = chef.Environment(chef_environment, chef_api)

        if env is None:
            self.logger.error('Cannot find/create chef environment')
            return self._fail()

        old_env_overrides = env.override_attributes

        self.logger.debug('Old environment overrides: %s' % old_env_overrides)

        # Find the node.  Sometimes chef takes a while to index; we will retry
        for i in range(3):
            if self._node_exists(node['name'], chef_api):
                break
            else:
                self.logger.info("Node '%s' is not registered with chef"
                                 "server.  Retrying %s/3)" % (
                                     node['name'], i + 1))
                time.sleep(10)
            if i == 3:
                msg = ("Node '%s' is not registered to chef.  "
                       "Exceeded max retries" % node['name'])
                self.logger.error(msg)
                return self._fail(msg=msg)

        chef_node = chef.Node(node['name'], chef_api)
        old_node_overrides = self._serialize_node_blob(chef_node.normal)

        self.logger.debug('Old node overrides: %s' % old_node_overrides)

        # we'll always converge node, just to be sure
        need_node_converge = False
        need_env_converge = False

        query = '"adventurator" in attrs.opencenter_agent_output_modules'
        adventurator = api._model_get_first_by_query('nodes', query)
        if not adventurator:
            self.logger.error('Could not find adventurator')
            return self._fail(msg='could not find adventurator')

        # we'll always stomp this stuff..

        # if old_node_overrides != node_attrs or \
        #         chef_node.chef_environment != chef_environment or\
        #         chef_node.run_list != self._map_roles(nova_role):
        self.logger.debug('Updating chef node')
        self.logger.debug('Setting environment to %s' % chef_environment)
        chef_node.chef_environment = chef_environment
        chef_node.normal = node_attrs
        old_runlist = chef_node.run_list
        chef_node.run_list = self._map_roles(nova_role)
        chef_node.save()

        if old_runlist != chef_node.run_list:
            # roles changed, refresh node and then all other nodes in env
            need_node_converge = True

        if old_env_overrides != env_attrs:
            # refresh entire environment in one go
            self.logger.debug('Updating environment')
            need_env_converge = True
            env.override_attributes = env_attrs
            env.save()

        if need_node_converge:
            # first run converge on the node in question
            self.logger.debug('chef updating node: %s' % node_id)
            result_b, result_s = self._watch_converge_task(
                api, [node_id])
            if result_b is not True:
                return self._fail(msg='First node pass: %s' % result_s)

            nodelist = self._get_nodes_in_env(chef_environment, api)
            if node_id in nodelist:
                nodelist.remove(node_id)

            self.logger.debug('chef updating env: %s: nodes %s' %
                              (chef_environment, nodelist))
            result_b, result_s = self._watch_converge_task(
                api, nodelist)
            if result_b is not True:
                return self._fail(msg='First env pass: %s' % result_s)

        if need_env_converge:
            # converge ALL of the nodes
            nodelist = self._get_nodes_in_env(chef_environment, api)
            self.logger.debug(nodelist % 'chef updating nodes: %s')

            result_b, result_s = self._watch_converge_task(
                api, nodelist)
            if result_b is not True:
                return self._fail(msg='First env pass: %s' % result_s)

        return self._ok()

    # README(shep): not executed on the server, skipping from code coverage
dataset/ETHPy150Open rcbops/opencenter/opencenter/backends/chef-client/__init__.py/ChefClientBackend.converge_chef/swapped operands of `%`