Example #1
0
class SaltStackInput(BaseInput):

    RESOURCE_MAP = {
        'salt_high_state': {
            'resource': 'High State',
            'icon': 'fa:cube',
        },
        'salt_job': {
            'resource': 'Job',
            'icon': 'fa:clock-o',
        },
        'salt_low_state': {
            'resource': 'Low State',
            'icon': 'fa:cube',
        },
        'salt_minion': {
            'resource': 'Minion',
            'icon': 'fa:server',
        },
        'salt_service': {
            'resource': 'Service',
            'icon': 'fa:podcast',
        },
        'salt_state_module': {
            'resource': 'State Module',
            'icon': 'fa:cubes',
        },
        'salt_user': {
            'resource': 'User',
            'icon': 'fa:user',
        },
    }

    def __init__(self, **kwargs):
        self.kind = 'salt'
        super(SaltStackInput, self).__init__(**kwargs)

        try:
            self.name = kwargs['name']
        except KeyError:
            raise ValueError('Missing parameter name')

        config_data = load_yaml_json_file(kwargs['config_file'])
        self.config = config_data['configs'][self.name]
        self.api = Pepper(self.config['url'])
        self.api.login(self.config['auth']['username'],
                       self.config['auth']['password'], 'pam')

    def scrape_all_resources(self):
        self.scrape_jobs()
        self.scrape_minions()
        self.scrape_services()
        self.scrape_high_states()
        # self.scrape_low_states()

    def _create_relations(self):
        """
        for resource_id, resource in self.resources['salt_low_state'].items():
            # Define relationships between low states and nodes.
            self._scrape_relation(
                'salt_minion-salt_low_state',
                resource['metadata']['minion'],
                resource_id)
            split_service = resource['metadata']['__sls__'].split('.')
            self._scrape_relation(
                'salt_service-salt_low_state',
                '{}|{}.{}'.format(resource['metadata']['minion'],
                                  split_service[0], split_service[1]),
                resource_id)
        """
        for resource_id, resource in self.resources.get('salt_high_state',
                                                        {}).items():
            # Define relationships between high states and nodes.
            self._scrape_relation('salt_minion-salt_high_state',
                                  resource['metadata']['minion'], resource_id)
            split_service = resource['metadata']['__sls__'].split('.')
            self._scrape_relation(
                'salt_service-salt_high_state',
                '{}|{}.{}'.format(resource['metadata']['minion'],
                                  split_service[0], split_service[1]),
                resource_id)

        for resource_id, resource in self.resources.get('salt_service',
                                                        {}).items():
            self._scrape_relation('salt_service-salt_minion', resource_id,
                                  resource['metadata']['host'])

        for resource_id, resource in self.resources.get('salt_job',
                                                        {}).items():
            self._scrape_relation('salt_user-salt_job',
                                  resource['metadata']['User'], resource_id)
            for minion_id, result in resource['metadata'].get('Result',
                                                              {}).items():
                self._scrape_relation('salt_job-salt_minion', resource_id,
                                      minion_id)
                if type(result) is list:
                    logger.error(result[0])
                else:
                    for state_id, state in result.items():
                        if '__id__' in state:
                            result_id = '{}|{}'.format(minion_id,
                                                       state['__id__'])
                            self._scrape_relation('salt_job-salt_high_state',
                                                  resource_id, result_id)

    def scrape_jobs(self):
        response = self.api.low([{
            'client':
            'runner',
            'fun':
            'jobs.list_jobs',
            'arg':
            "search_function='[\"state.apply\", \"state.sls\"]'"
        }]).get('return')[0]
        for job_id, job in response.items():
            if job['Function'] in ['state.apply', 'state.sls']:
                result = self.api.lookup_jid(job_id).get('return')[0]
                job['Result'] = result
                self._scrape_resource(job_id,
                                      job['Function'],
                                      'salt_job',
                                      None,
                                      metadata=job)
                self._scrape_resource(job['User'],
                                      job['User'],
                                      'salt_user',
                                      None,
                                      metadata={})

    def scrape_minions(self):
        response = self.api.low([{
            'client': 'local',
            'tgt': '*',
            'fun': 'grains.items'
        }]).get('return')[0]
        for minion_id, minion in response.items():
            self._scrape_resource(minion_id,
                                  minion_id,
                                  'salt_minion',
                                  None,
                                  metadata=minion)

    def scrape_services(self):
        response = self.api.low([{
            'client': 'local',
            'expr_form': 'compound',
            'tgt': 'I@salt:master',
            'fun': 'saltresource.graph_data'
        }]).get('return')[0]
        for minion_id, minion in response.items():
            for service in minion['graph']:
                self._scrape_resource('{}|{}'.format(minion_id,
                                                     service['service']),
                                      service['service'],
                                      'salt_service',
                                      None,
                                      metadata=service)

    def scrape_low_states(self):
        response = self.api.low([{
            'client': 'local',
            'tgt': '*',
            'fun': 'state.show_lowstate'
        }]).get('return')[0]
        for minion_id, low_states in response.items():
            for low_state in low_states:
                low_state['minion'] = minion_id
                self._scrape_resource('{}|{}|{}'.format(
                    minion_id, low_state['state'], low_state['__id__']),
                                      '{} {}'.format(low_state['state'],
                                                     low_state['__id__']),
                                      'salt_low_state',
                                      None,
                                      metadata=low_state)

    def scrape_high_states(self):
        response = self.api.low([{
            'client': 'local',
            'tgt': '*',
            'fun': 'state.show_highstate'
        }]).get('return')[0]
        for minion_id, high_states in response.items():
            if type(high_states) is list:
                logger.error(high_states[0])
            else:
                for high_state_id, high_state in high_states.items():
                    high_state['minion'] = minion_id
                    self._scrape_resource('{}|{}'.format(
                        minion_id, high_state_id),
                                          high_state_id,
                                          'salt_high_state',
                                          None,
                                          metadata=high_state)
Example #2
0
class PepperApi():
    def __init__(self):
        self.authenticated = False
        self.api = Pepper(os.environ.get("SALT_HOST"))
        self.api_auth()

    def api_auth(self):
        try:
            self.api.login(os.environ.get("SALT_USERNAME"),
                           os.environ.get("SALT_SHARED_SECRET"),
                           'sharedsecret')
            self.authenticated = True
        except Exception as err:
            logger.error("Error authing to salt: {0}".format(err))
            self.authenticated = False

    def salt_keys(self):
        """
        Queries the Salt Master for all Minion keys

        Returns:
            dict: A dictionary with 3 lists for minions,
            pre-minions and rejected minions
        """
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'wheel',
            'fun': 'key.list_all'
        }])
        if api_reponse['return'][0]['data']['success']:
            return api_reponse['return'][0]['data']['return']

    def accept_key(self, minion_id):
        """
        Tells the Salt Master to Accept a Key by its name

        Args:
            minion_id (str): Minion ID -
            should be a Mongo ID from `Hive` modal

        Returns:
            dict: A dictionary with the ID for any accepted minions.
        """
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'wheel',
            'fun': 'key.accept',
            'match': minion_id
        }])
        if api_reponse['return'][0]['data']['success']:
            return api_reponse['return'][0]['data']['return']

    def delete_key(self, minion_id):
        """Given a Minion ID will delete the key from the salt master"""
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'wheel',
            'fun': 'key.delete',
            'match': minion_id
        }])
        if api_reponse['return'][0]['data']['success']:
            return api_reponse['return'][0]['data']['return']

    def run_client_function_async(self, target, function, arg_list=[]):
        """
        Basic Function Runner; for things like test.ping it is up to
        the receiving function to parse the data it needs.
        Makes Async Call returns Job ID
        """
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'local_async',
            'tgt': target,
            'fun': function,
            'arg': arg_list
        }])
        return api_reponse['return'][0]['jid']

    def run_client_function(self, target, function, arg_list=[]):
        """
        Basic Function Runner; for things like test.ping it
        is up to the receiving function to parse the data it needs.
        Not async so blocking?
        """
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'local',
            'tgt': target,
            'fun': function,
            'arg': arg_list
        }])

        return api_reponse['return'][0]

    def apply_state(self, target, args_list):
        """
        Tells the Salt Master to apply a given state to a named minion

        Args:
            target (str): Minion ID - should be a Mongo ID
            from `Hive` modal
            args_list (list): List of args. First element in list MUST
            be the Salt State to apply

        Returns:
            str: Salt Job ID
        """
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'local_async',
            'tgt': target,
            'fun': "state.apply",
            'arg': args_list
        }])
        return api_reponse['return'][0]['jid']

    def lookup_job(self, job_id):
        """
        Query the Salt Master for a job by its Salt Job ID

        Args:
            job_id (str): Salt Job Id should be taken from PepperJobs Model

        Returns:
            dict/None: Return None if job still pending else dictionary of
            all state modules and their outputs.
        """
        # check auth state
        self.api_auth()
        try:
            api_response = self.api.lookup_jid(job_id)
            if len(api_response['return'][0]) == 0:
                return None
            else:
                return api_response['return'][0]
        except Exception as err:
            logger.error("Error authing to salt: {0}".format(err))
        return None

    def docker_state(self, target, container):
        """
        Get the status of a docker container.
        """
        # check auth state
        self.api_auth()
        api_reponse = self.api.low([{
            'client': 'local_async',
            'tgt': target,
            'fun': 'docker.state',
            'arg': container
        }])

        return api_reponse['return'][0]['jid']

    def docker_control(self, target, container, wanted_state):
        """
        Set the status of a container.
        The container must already exist
        """
        # check auth state
        self.api_auth()

        if wanted_state == "start":
            function = "docker.start"
        elif wanted_state == "stop":
            function = "docker.stop"

        api_reponse = self.api.low([{
            'client': 'local_async',
            'tgt': target,
            'fun': function,
            'arg': container
        }])

        return api_reponse['return'][0]['jid']

    def docker_remove(self, target, container):
        """
        Remove a container and destroy its image
        """
        # check auth state
        self.api_auth()

        responses = []

        # Stop the container
        api_reponse = self.api.low([{
            'client': 'local',
            'tgt': target,
            'fun': 'docker.stop',
            'arg': container
        }])
        responses.append(api_reponse)

        # Remove the container
        api_reponse = self.api.low([{
            'client': 'local',
            'tgt': target,
            'fun': 'docker.rm',
            'arg': container
        }])
        responses.append(api_reponse)

        # Check state to confirm
        api_reponse = self.api.low([{
            'client': 'local',
            'tgt': target,
            'fun': 'docker.state',
            'arg': container
        }])

        try:
            result_object = api_reponse['return'][0]
            if 'ERROR' in result_object[target]:
                return True
            else:
                return False

        except Exception as err:
            logging.error(err)
            return False
Example #3
0
class SaltStackClient(BaseClient):
    def __init__(self, **kwargs):
        super(SaltStackClient, self).__init__(**kwargs)

    def auth(self):
        status = True
        try:
            self.api = Pepper(self.metadata['auth_url'])
            self.api.login(self.metadata['username'],
                           self.metadata['password'], 'pam')
        except PepperException as exception:
            logger.error(exception)
            status = False
        except URLError as exception:
            logger.error(exception)
            status = False
        return status

    def update_resources(self, resources=None):
        if self.auth():
            if resources is None:
                resources = DEFAULT_RESOURCES
            for resource in resources:
                metadata = self.get_resource_metadata(resource)
                self.process_resource_metadata(resource, metadata)
                count = len(self.resources.get(resource, {}))
                logger.info("Processed {} {} resources".format(
                    count, resource))
            self.process_relation_metadata()

    def get_resource_status(self, kind, metadata):
        if not isinstance(metadata, dict):
            return 'unknown'
        if kind == 'salt_minion':
            if 'id' in metadata:
                return 'active'
        return 'unknown'

    def get_resource_metadata(self, kind):
        logger.info("Getting {} resources".format(kind))
        if kind == 'salt_job':
            metadata = self.api.low([{
                'client': 'runner',
                'fun': 'jobs.list_jobs',
                'arg': "search_function='[\"state.apply\", \"state.sls\"]'",
                'timeout': 60
            }]).get('return')[0]
        elif kind == 'salt_lowstate':
            metadata = self.api.low([{
                'client': 'local',
                'tgt': '*',
                'fun': 'state.show_lowstate',
                'timeout': 60
            }]).get('return')[0]
        elif kind == 'salt_minion':
            metadata = self.api.low([{
                'client': 'local',
                'tgt': '*',
                'fun': 'grains.items',
                'timeout': 60
            }]).get('return')[0]
        elif kind == 'salt_service':
            metadata = self.api.low([{
                'client': 'local',
                'tgt': '*',
                'fun': 'pillar.data',
                'timeout': 60
            }]).get('return')[0]
        else:
            metadata = {}
        if not isinstance(metadata, dict):
            metadata = {}
        return metadata

    def process_resource_metadata(self, kind, metadata):
        if kind == 'salt_event':
            manager = Manager.objects.get(name=metadata.get('manager'))
            roles = []
            if isinstance(metadata.get('return'), (list, tuple)):
                return
            for datum_name, datum in metadata.get('return', {}).items():
                try:
                    uid = '{}|{}'.format(metadata['id'], datum['__id__'])
                except KeyError as exception:
                    logger.error('No key {} in {}'.format(exception, datum))
                    continue
                try:
                    lowstate = Resource.objects.get(uid=uid, manager=manager)
                except Resource.DoesNotExist:
                    logger.error('No salt_lowstate resource '
                                 'with UID {} found'.format(uid))
                    continue
                to_save = False

                if 'apply' not in lowstate.metadata:
                    lowstate.metadata['apply'] = {}
                    lowstate.metadata['apply'][metadata.get('jid')] = datum
                    if datum['result']:
                        lowstate.status = 'active'
                    else:
                        lowstate.status = 'error'
                    to_save = True
                else:
                    if metadata.get('jid') not in lowstate.metadata['apply']:
                        lowstate.metadata['apply'][metadata.get('jid')] = datum
                        if datum['result']:
                            lowstate.status = 'active'
                        else:
                            lowstate.status = 'error'
                        to_save = True
                if to_save:
                    lowstate.save()
                    role_parts = lowstate.metadata['__sls__'].split('.')
                    role_name = "{}-{}".format(role_parts[0], role_parts[1])
                    roles.append(role_name)

            for role_name in set(roles):
                uid = '{}|{}'.format(metadata['id'], role_name)
                try:
                    service = Resource.objects.get(uid=uid, manager=manager)
                except Resource.DoesNotExist:
                    logger.error('No salt_service resource '
                                 'with UID {} found'.format(uid))
                    continue
                errors = 0
                unknown = 0
                to_save = False
                lowstate_links = service.source.filter(kind='state_of_service')
                for lowstate_link in lowstate_links:
                    if lowstate_link.target.status == 'error':
                        errors += 1
                    elif lowstate_link.target.status == 'unknown':
                        unknown += 1
                if errors > 0 and service.status != 'error':
                    service.status = 'error'
                    to_save = True
                if unknown > 0 and service.status != 'unknown':
                    service.status = 'build'
                    to_save = True
                elif service.status != 'active':
                    service.status = 'active'
                    to_save = True
                if to_save:
                    service.save()
        elif kind == 'salt_job':
            for job_id, job in metadata.items():
                if not isinstance(job, dict):
                    continue
                if job['Function'] in ['state.apply', 'state.sls']:
                    result = self.api.lookup_jid(job_id).get('return')[0]
                    job['Result'] = result
                    self._create_resource(job_id,
                                          job['Function'],
                                          'salt_job',
                                          metadata=job)
                    self._create_resource(job['User'],
                                          job['User'].replace('sudo_', ''),
                                          'salt_user',
                                          metadata={})
        elif kind == 'salt_lowstate':
            for minion_id, low_states in metadata.items():
                if not isinstance(low_states, list):
                    continue
                for low_state in low_states:
                    if not isinstance(low_state, dict):
                        logger.error('Salt lowtate {} parsing problem on '
                                     '{}'.format(low_state, minion_id))
                        continue

                    low_state['minion'] = minion_id
                    self._create_resource(
                        '{}|{}'.format(minion_id, low_state['__id__']),
                        '{} {}'.format(low_state['state'],
                                       low_state['__id__']),
                        'salt_lowstate',
                        metadata=low_state)
        elif kind == 'salt_minion':
            self._create_resource('salt-master',
                                  'salt-master',
                                  'salt_master',
                                  metadata={})
            for minion_id, minion_data in metadata.items():
                self._create_resource(minion_id,
                                      minion_id,
                                      'salt_minion',
                                      metadata={'grains': minion_data})
        elif kind == 'salt_service':
            for minion_id, minion_data in metadata.items():
                if not isinstance(minion_data, dict):
                    continue
                for service_name, service in minion_data.items():
                    if service_name not in settings.RECLASS_SERVICE_BLACKLIST:
                        if not isinstance(service, dict):
                            logger.error('Salt service {} parsing problem: '
                                         '{} on {}'.format(
                                             service_name, service, minion_id))
                            continue
                        for role_name, role in service.items():
                            if role_name not in settings.RECLASS_ROLE_BLACKLIST:
                                service_key = '{}-{}'.format(
                                    service_name, role_name)
                                self._create_resource('{}|{}'.format(
                                    minion_id, service_key),
                                                      service_key,
                                                      'salt_service',
                                                      metadata={
                                                          'pillar': {
                                                              service_name: {
                                                                  role_name:
                                                                  role
                                                              }
                                                          }
                                                      })

    def process_relation_metadata(self):
        # Define relationships between minions and master
        for resource_id, resource in self.resources.get('salt_minion',
                                                        {}).items():
            self._create_relation('controlled_by_master', resource_id,
                                  'salt-master')

        # Define relationships between services and minions
        for resource_id, resource in self.resources.get('salt_service',
                                                        {}).items():
            self._create_relation('runs_on_minion', resource_id,
                                  resource_id.split('|')[0])

        # Define relationships between lowstates and services
        for resource_id, resource in self.resources.get('salt_lowstate',
                                                        {}).items():
            split_service = resource['metadata']['__sls__'].split('.')
            self._create_relation(
                'state_of_service', resource_id,
                '{}|{}-{}'.format(resource['metadata']['minion'],
                                  split_service[0], split_service[1]))

        for resource_id, resource in self.resources.get('salt_job',
                                                        {}).items():
            self._create_relation('action_by_user', resource_id,
                                  resource['metadata']['User'])
            for minion_id, result in resource['metadata'].get('Result',
                                                              {}).items():
                self._create_relation('applied_on_minion', resource_id,
                                      minion_id)
                if type(result) is list:
                    logger.error(result[0])
                else:
                    for state_id, state in result.items():
                        if '__id__' in state:
                            result_id = '{}|{}'.format(minion_id,
                                                       state['__id__'])
                            self._create_relation('applied_lowstate',
                                                  resource_id, result_id)

    def get_resource_action_fields(self, resource, action):
        fields = {}
        if resource.kind == 'salt_minion':
            if action == 'run_module':
                fields['function'] = forms.CharField(label='Module function',
                                                     initial='cmd.run')
                fields['arguments'] = forms.CharField(
                    widget=forms.Textarea(attrs={
                        'rows': 3,
                        'cols': 40
                    }),
                    required=False,
                    label='Function arguments')
        return fields

    def process_resource_action(self, resource, action, data):
        if resource.kind == 'salt_minion':
            if action == 'run_module':
                if self.auth():
                    run_metadata = {
                        'client': 'local',
                        'tgt': resource.uid,
                        'fun': data['function'],
                        'timeout': 60
                    }
                    if data['arguments'] != '':
                        run_metadata['arg'] = [
                            s.strip() for s in data['arguments'].splitlines()
                        ]
                    metadata = self.api.low([run_metadata]).get('return')[0]
                    logger.info(metadata)
Example #4
0
class SaltStackInput(BaseInput):
    def __init__(self, **kwargs):
        self.kind = 'salt'
        super(SaltStackInput, self).__init__(**kwargs)
        self.api = Pepper(self.config['auth_url'])
        self.api.login(self.config['username'], self.config['password'], 'pam')

    def scrape_all_resources(self):
        self.scrape_jobs()
        self.scrape_minions()
        self.scrape_services()
        self.scrape_high_states()
        # self.scrape_low_states()

    def _create_relations(self):
        """
        for resource_id, resource in self.resources['salt_low_state'].items():
            # Define relationships between low states and nodes.
            self._scrape_relation(
                'salt_minion-salt_low_state',
                resource['metadata']['minion'],
                resource_id)
            split_service = resource['metadata']['__sls__'].split('.')
            self._scrape_relation(
                'salt_service-salt_low_state',
                '{}|{}.{}'.format(resource['metadata']['minion'],
                                  split_service[0], split_service[1]),
                resource_id)
        """
        for resource_id, resource in self.resources.get('salt_high_state',
                                                        {}).items():
            # Define relationships between high states and nodes.
            self._scrape_relation('on_salt_minion', resource_id,
                                  resource['metadata']['minion'])
            split_service = resource['metadata']['__sls__'].split('.')
            self._scrape_relation(
                'contains_salt_high_state',
                '{}|{}.{}'.format(resource['metadata']['minion'],
                                  split_service[0], split_service[1]),
                resource_id)

        for resource_id, resource in self.resources.get('salt_service',
                                                        {}).items():
            self._scrape_relation('on_salt_minion', resource_id,
                                  resource['metadata']['host'])

        for resource_id, resource in self.resources.get('salt_job',
                                                        {}).items():
            self._scrape_relation('by_salt_user', resource_id,
                                  resource['metadata']['User'])
            for minion_id, result in resource['metadata'].get('Result',
                                                              {}).items():
                self._scrape_relation('on_salt_minion', resource_id, minion_id)
                if type(result) is list:
                    logger.error(result[0])
                else:
                    for state_id, state in result.items():
                        if '__id__' in state:
                            result_id = '{}|{}'.format(minion_id,
                                                       state['__id__'])
                            self._scrape_relation('contains_salt_high_state',
                                                  resource_id, result_id)

    def scrape_jobs(self):
        response = self.api.low([{
            'client':
            'runner',
            'fun':
            'jobs.list_jobs',
            'arg':
            "search_function='[\"state.apply\", \"state.sls\"]'"
        }]).get('return')[0]
        for job_id, job in response.items():
            if job['Function'] in ['state.apply', 'state.sls']:
                result = self.api.lookup_jid(job_id).get('return')[0]
                job['Result'] = result
                self._scrape_resource(job_id,
                                      job['Function'],
                                      'salt_job',
                                      None,
                                      metadata=job)
                self._scrape_resource(job['User'],
                                      job['User'],
                                      'salt_user',
                                      None,
                                      metadata={})

    def scrape_minions(self):
        response = self.api.low([{
            'client': 'local',
            'tgt': '*',
            'fun': 'grains.items'
        }]).get('return')[0]
        for minion_id, minion in response.items():
            self._scrape_resource(minion_id,
                                  minion_id,
                                  'salt_minion',
                                  None,
                                  metadata=minion)

    def scrape_services(self):
        response = self.api.low([{
            'client': 'local',
            'expr_form': 'compound',
            'tgt': 'I@salt:master',
            'fun': 'saltresource.graph_data'
        }]).get('return')[0]
        for minion_id, minion in response.items():
            for service in minion['graph']:
                self._scrape_resource('{}|{}'.format(minion_id,
                                                     service['service']),
                                      service['service'],
                                      'salt_service',
                                      None,
                                      metadata=service)

    def scrape_low_states(self):
        response = self.api.low([{
            'client': 'local',
            'tgt': '*',
            'fun': 'state.show_lowstate'
        }]).get('return')[0]
        for minion_id, low_states in response.items():
            for low_state in low_states:
                low_state['minion'] = minion_id
                self._scrape_resource('{}|{}|{}'.format(
                    minion_id, low_state['state'], low_state['__id__']),
                                      '{} {}'.format(low_state['state'],
                                                     low_state['__id__']),
                                      'salt_low_state',
                                      None,
                                      metadata=low_state)

    def scrape_high_states(self):
        response = self.api.low([{
            'client': 'local',
            'tgt': '*',
            'fun': 'state.show_highstate'
        }]).get('return')[0]
        for minion_id, high_states in response.items():
            if type(high_states) is list:
                logger.error(high_states[0])
            else:
                for high_state_id, high_state in high_states.items():
                    high_state['minion'] = minion_id
                    self._scrape_resource('{}|{}'.format(
                        minion_id, high_state_id),
                                          high_state_id,
                                          'salt_high_state',
                                          None,
                                          metadata=high_state)