def mock_puppetdb_default_nodes(mocker): node_list = [ Node('_', 'node-unreported', report_timestamp='2013-08-01T09:57:00.000Z', latest_report_hash='1234567', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status_report='unreported'), Node('_', 'node-changed', report_timestamp='2013-08-01T09:57:00.000Z', latest_report_hash='1234567', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status_report='changed'), Node('_', 'node-failed', report_timestamp='2013-08-01T09:57:00.000Z', latest_report_hash='1234567', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status_report='failed'), Node('_', 'node-noop', report_timestamp='2013-08-01T09:57:00.000Z', latest_report_hash='1234567', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status_report='noop'), Node('_', 'node-unchanged', report_timestamp='2013-08-01T09:57:00.000Z', latest_report_hash='1234567', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status_report='unchanged'), ] return mocker.patch.object(app.puppetdb, 'nodes', return_value=iter(node_list))
def test_with_cached_catalog_status(self): node1 = Node('_', 'node', cached_catalog_status='explicitly_requested') node2 = Node('_', 'node', cached_catalog_status='on_failure') node3 = Node('_', 'node', cached_catalog_status='not_used') assert node1.name == 'node' assert node1.cached_catalog_status == 'explicitly_requested' assert node2.name == 'node' assert node2.cached_catalog_status == 'on_failure' assert node3.name == 'node' assert node3.cached_catalog_status == 'not_used'
def nodes(self, name=None, query=None): """Query for nodes by either name or query. If both aren't provided this will return a list of all nodes. :param name: (optional) :type name: :obj:`None` or :obj:`string` :param query: (optional) :type query: :obj:`None` or :obj:`string` :returns: A generator yieling Nodes. :rtype: :class:`pypuppetdb.types.Node` """ nodes = self._query('nodes', path=name, query=query) # If we happen to only get one node back it # won't be inside a list so iterating over it # goes boom. Therefor we wrap a list around it. if type(nodes) == dict: log.debug("Request returned a single node.") nodes = [ nodes, ] for node in nodes: yield Node( self, node['name'], deactivated=node['deactivated'], report_timestamp=node['report_timestamp'], catalog_timestamp=node['catalog_timestamp'], facts_timestamp=node['facts_timestamp'], )
def test_apiv4_with_unchanged_status(self): node = Node( '_', 'node', status='unchanged', report_environment='development', catalog_environment='development', facts_environment='development', report_timestamp='2013-08-01T09:57:00.000Z', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', ) assert node.name == 'node' assert node.deactivated is False assert node.expired is False assert node.report_environment == 'development' assert node.catalog_environment == 'development' assert node.facts_environment == 'development' assert node.report_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.facts_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.catalog_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.status == 'unchanged' assert str(node) == str('node') assert unicode(node) == unicode('node') assert repr(node) == str('<Node: node>')
def test_apiv4_with_failed_noop_status(self): node = Node( '_', 'node', status_report='failed', noop=True, noop_pending=False, report_environment='development', catalog_environment='development', facts_environment='development', report_timestamp='2013-08-01T09:57:00.000Z', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', ) assert node.name == 'node' assert node.deactivated is False assert node.expired is False assert node.report_environment == 'development' assert node.catalog_environment == 'development' assert node.facts_environment == 'development' assert node.report_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.facts_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.catalog_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.status == 'failed' assert str(node) == str('node') assert str(node) == str('node') assert repr(node) == str('<Node: node>')
def test_with_status_unreported(self): node = Node( '_', 'node', report_timestamp='2013-08-01T09:57:00.000Z', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status='unreported', unreported_time='0d 5h 20m', ) assert node.name == 'node' assert node.deactivated is False assert node.expired is False assert node.report_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.facts_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.catalog_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.status is 'unreported' assert node.unreported_time is '0d 5h 20m' assert str(node) == str('node') assert unicode(node) == unicode('node') assert repr(node) == str('<Node: node>')
def test_expired(self): node = Node( '_', 'node', expired='2013-08-01T09:57:00.000Z', ) assert node.name == 'node' assert node.expired == json_to_datetime('2013-08-01T09:57:00.000Z') assert str(node) == str('node') assert unicode(node) == unicode('node') assert repr(node) == str('<Node: node>')
def nodes(self, unreported=2, with_status=False, with_event_numbers=True, **kwargs): """Query for nodes by either name or query. If both aren't provided this will return a list of all nodes. This method also (optionally) fetches the nodes status and (optionally) event counts of the latest report from puppetdb. :param with_status: (optional) include the node status in the\ returned nodes :type with_status: :bool: :param unreported: (optional) amount of hours when a node gets marked as unreported :type unreported: :obj:`None` or integer :param with_event_numbers: (optional) include the exact number of\ changed/unchanged/failed/noop events when\ with_status is set to True. If set to False only "some" string is provided if there are resources with such status in the last report. This provides performance benefits as potentially slow event-counts query is omitted completely. :type with_event_numbers: :bool: :param \*\*kwargs: The rest of the keyword arguments are passed to the _query function :returns: A generator yieling Nodes. :rtype: :class:`pypuppetdb.types.Node` """ nodes = self._query('nodes', **kwargs) now = datetime.utcnow() # If we happen to only get one node back it # won't be inside a list so iterating over it # goes boom. Therefor we wrap a list around it. if type(nodes) == dict: nodes = [ nodes, ] latest_events = None if with_status and with_event_numbers: latest_events = self._query( 'event-counts', query=EqualsOperator("latest_report?", True), summarize_by='certname', ) for node in nodes: yield Node.create_from_dict(self, node, with_status, with_event_numbers, latest_events, now, unreported)
def test_deactivated(self): node = Node( '_', 'node', deactivated='2013-08-01T09:57:00.000Z', ) assert node.name == 'node' assert node.deactivated == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert str(node) == str('node') assert str(node) == str('node') assert repr(node) == str('<Node: node>')
def test_node(): node1 = Node( '_', 'node1.puppet.board', report_timestamp='2013-08-01T09:57:00.000Z', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', status='unreported', unreported_time='0d 5h 20m', ) node2 = Node( '_', 'node2.puppet.board', deactivated='2013-08-01T09:57:00.000Z', report_timestamp=None, catalog_timestamp=None, facts_timestamp=None, ) assert node1.name == 'node1.puppet.board' assert node1.deactivated is False assert node1.report_timestamp is not None assert node1.facts_timestamp is not None assert node1.catalog_timestamp is not None assert node1.status is 'unreported' assert node1.unreported_time is '0d 5h 20m' assert str(node1) == str('node1.puppet.board') assert repr(node1) == str('<Node: node1.puppet.board>') assert node2.name == 'node2.puppet.board' assert node2.deactivated is not False assert node2.report_timestamp is None assert node2.catalog_timestamp is None assert node2.facts_timestamp is None assert str(node2) == str('node2.puppet.board') assert repr(node2) == str('<Node: node2.puppet.board>')
def test_without_status(self): node = Node( '_', 'node', report_timestamp='2013-08-01T09:57:00.000Z', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', ) assert node.name == 'node' assert node.deactivated is False assert node.report_timestamp is not None assert node.facts_timestamp is not None assert node.catalog_timestamp is not None assert str(node) == str('node') assert unicode(node) == unicode('node') assert repr(node) == str('<Node: node>')
def mock_puppetdb_default_nodes(mocker): timestamp = '2013-08-01T09:57:00.000Z' report_hash = '1234567' transaction = '7890' version = '3.8.5' node_list = [ Node('_', 'node-%s' % status, report_timestamp=timestamp, latest_report_hash=report_hash, catalog_timestamp=timestamp, facts_timestamp=timestamp, status_report=status, report=Report('_', 'node-%s', report_hash, timestamp, timestamp, timestamp, version, '6', version, transaction)) for status in ['failed', 'changed', 'unchanged', 'noop', 'unreported'] ] return mocker.patch.object(app.puppetdb, 'nodes', return_value=iter(node_list))
def test_without_status(self): node = Node( '_', 'node', report_timestamp='2013-08-01T09:57:00.000Z', catalog_timestamp='2013-08-01T09:57:00.000Z', facts_timestamp='2013-08-01T09:57:00.000Z', ) assert node.name == 'node' assert node.deactivated is False assert node.expired is False assert node.report_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.facts_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert node.catalog_timestamp == \ json_to_datetime('2013-08-01T09:57:00.000Z') assert str(node) == str('node') assert str(node) == str('node') assert repr(node) == str('<Node: node>')
def nodes(self, unreported=2, with_status=False, **kwargs): """Query for nodes by either name or query. If both aren't provided this will return a list of all nodes. This method also fetches the nodes status and event counts of the latest report from puppetdb. :param with_status: (optional) include the node status in the\ returned nodes :type with_status: :bool: :param unreported: (optional) amount of hours when a node gets marked as unreported :type unreported: :obj:`None` or integer :param \*\*kwargs: The rest of the keyword arguments are passed to the _query function :returns: A generator yieling Nodes. :rtype: :class:`pypuppetdb.types.Node` """ nodes = self._query('nodes', **kwargs) now = datetime.datetime.utcnow() # If we happen to only get one node back it # won't be inside a list so iterating over it # goes boom. Therefor we wrap a list around it. if type(nodes) == dict: nodes = [ nodes, ] if with_status: latest_events = self.event_counts(query=EqualsOperator( "latest_report?", True), summarize_by='certname') for node in nodes: node['status_report'] = None node['events'] = None if with_status: status = [ s for s in latest_events if s['subject']['title'] == node['certname'] ] try: node['status_report'] = node['latest_report_status'] if status: node['events'] = status[0] except KeyError: if status: node['events'] = status = status[0] if status['successes'] > 0: node['status_report'] = 'changed' if status['noops'] > 0: node['status_report'] = 'noop' if status['failures'] > 0: node['status_report'] = 'failed' else: node['status_report'] = 'unchanged' # node report age if node['report_timestamp'] is not None: try: last_report = json_to_datetime( node['report_timestamp']) last_report = last_report.replace(tzinfo=None) unreported_border = now - timedelta(hours=unreported) if last_report < unreported_border: delta = (now - last_report) node['unreported'] = True node['unreported_time'] = '{0}d {1}h {2}m'.format( delta.days, int(delta.seconds / 3600), int((delta.seconds % 3600) / 60)) except AttributeError: node['unreported'] = True if not node['report_timestamp']: node['unreported'] = True yield Node(self, name=node['certname'], deactivated=node['deactivated'], expired=node['expired'], report_timestamp=node['report_timestamp'], catalog_timestamp=node['catalog_timestamp'], facts_timestamp=node['facts_timestamp'], status_report=node['status_report'], noop=node.get('latest_report_noop'), noop_pending=node.get('latest_report_noop_pending'), events=node['events'], unreported=node.get('unreported'), unreported_time=node.get('unreported_time'), report_environment=node['report_environment'], catalog_environment=node['catalog_environment'], facts_environment=node['facts_environment'], latest_report_hash=node.get('latest_report_hash'), cached_catalog_status=node.get('cached_catalog_status'))
def nodes(self, name=None, query=None, unreported=2, with_status=False): """Query for nodes by either name or query. If both aren't provided this will return a list of all nodes. This method also fetches the nodes status and event counts of the latest report from puppetdb. :param name: (optional) :type name: :obj:`None` or :obj:`string` :param query: (optional) :type query: :obj:`None` or :obj:`string` :param with_status: (optional) include the node status in the\ returned nodes :type with_status: :bool: :param unreported: (optional) amount of hours when anode gets marked as unreported :type unreported: :obj:`None` or integer :returns: A generator yieling Nodes. :rtype: :class:`pypuppetdb.types.Node` """ nodes = self._query('nodes', path=name, query=query) # If we happen to only get one node back it # won't be inside a list so iterating over it # goes boom. Therefor we wrap a list around it. if type(nodes) == dict: nodes = [ nodes, ] if with_status: latest_events = self._query('event-counts', query='["=","latest-report?",true]', summarize_by='certname') for node in nodes: node['unreported_time'] = None node['status'] = None if with_status: status = [ s for s in latest_events if s['subject']['title'] == node['name'] ] # node status from events if with_status and status: node['events'] = status = status[0] if status['successes'] > 0: node['status'] = 'changed' if status['failures'] > 0: node['status'] = 'failed' else: node['status'] = 'unchanged' node['events'] = None # node report age if with_status and node['report_timestamp'] is not None: try: last_report = json_to_datetime(node['report_timestamp']) last_report = last_report.replace(tzinfo=None) now = datetime.utcnow() unreported_border = now - timedelta(hours=unreported) if last_report < unreported_border: delta = (datetime.utcnow() - last_report) node['status'] = 'unreported' node['unreported_time'] = '{0}d {1}h {2}m'.format( delta.days, int(delta.seconds / 3600), int((delta.seconds % 3600) / 60)) except AttributeError: node['status'] = 'unreported' if not node['report_timestamp']: node['status'] = 'unreported' yield Node(self, node['name'], deactivated=node['deactivated'], report_timestamp=node['report_timestamp'], catalog_timestamp=node['catalog_timestamp'], facts_timestamp=node['facts_timestamp'], status=node['status'], events=node['events'], unreported_time=node['unreported_time'])
def test_with_latest_report_hash(self): node = Node('_', 'node', latest_report_hash='hash#1') assert node.name == 'node' assert node.latest_report_hash == 'hash#1'
def pql(self, pql, with_status=False, unreported=2, with_event_numbers=True): """Makes a PQL (Puppet Query Language) and tries to cast results to a rich type. If it won't work, returns plain dicts. :param pql: PQL query :type pql: :obj:`string` :param with_status: (optional, only for queries for nodes) include the node status in the returned nodes :type with_status: :bool: :param unreported: (optional, only for queries for nodes) amount of hours when a node gets marked as unreported :type unreported: :obj:`None` or integer :param with_event_numbers: (optional, only for queries for nodes) include the exact number of changed/unchanged/failed/noop events when with_status is set to True. If set to False only "some" string is provided if there are resources with such status in the last report. This provides performance benefits as potentially slow event-counts query is omitted completely. :type with_event_numbers: :bool: :returns: A generator yielding elements of a rich type or plain dicts """ type_class = self._get_type_from_query(pql) if type_class == Node and (with_status or unreported != 2 or not with_event_numbers): log.error( "with_status, unreported and with_event_numbers are used only" " for queries for nodes!") raise APIError for element in self._pql(pql=pql): if type_class == Node: # TODO: deduplicate this - see QueryAPI.nodes() now = datetime.utcnow() latest_events = None if with_status and with_event_numbers: latest_events = self._query( 'event-counts', query=EqualsOperator("latest_report?", True), summarize_by='certname', ) yield Node.create_from_dict(self, element, with_status, with_event_numbers, latest_events, now, unreported) elif type_class == Report: yield Report.create_from_dict(self, element) elif type_class: yield type_class.create_from_dict(element) else: yield element