Source code for hammers.query

# coding: utf-8
from __future__ import absolute_import, print_function, unicode_literals
import functools

LIBERTY = 'liberty'
OCATA = 'ocata'
QUERIES = {}


[docs]def query(q): '''Decorator to include all the queries into a dictionary''' global QUERIES QUERIES[q.__name__] = {'f': q} return q
[docs]def project_col(version): ''' The name of the column changed somewhere between L and O. Should be pretty basic to avoid any SQL injection. ''' # might also be sensitive to the component (neutron/nova/etc.) return { LIBERTY: 'tenant_id', OCATA: 'project_id', }[version]
[docs]@query def idle_projects(db): ''' Returns rows enumerating all projects that are currently idle (number of running instances = 0). Also provides since when the project has been idle (when the latest running instance was deleted) There may be NULLs emitted for "latest_deletion" if a project hasn't ever had an instance (like an admin project...). ''' sql_template = ''' SELECT project.id , project.name # , Count(ip.{projcol}) , (SELECT deleted_at FROM {novatable}.instances AS instance WHERE instance.project_id = project.id ORDER BY deleted_at DESC LIMIT 1) AS latest_deletion FROM neutron.floatingips AS ip , keystone.project AS project WHERE ip.{projcol} = project.id AND ip.status = "down" AND (SELECT Count(*) FROM {novatable}.instances AS instance WHERE instance.project_id = project.id AND deleted_at IS NULL AND vm_state != "deleted" AND vm_state != "error") = 0 GROUP BY {projcol} ORDER BY Count({projcol}) DESC; ''' projcol = project_col(db.version) old_sql = sql_template.format(projcol=projcol, novatable='nova') if db.version == 'ocata': # smash together data from two databases. newer openstack split the database # into nova (legacy?) and nova_cell0 (new schema? distributed?) new_sql = sql_template.format(projcol=projcol, novatable='nova_cell0') merged = list(db.query(old_sql, limit=None)) + list(db.query(new_sql, limit=None)) latest_for_project = {} for row in merged: if row['id'] in latest_for_project: # no date = ignore if latest_for_project[row['id']] is None: # already ignored continue if row['latest_deletion'] is None: # ignored from now on latest_for_project[row['id']]['latest_deletion'] = None continue latest_for_project[row['id']]['latest_deletion'] = max( # otherwise get latest row['latest_deletion'], latest_for_project[row['id']]['latest_deletion'] ) else: latest_for_project[row['id']] = row results = [row for row in latest_for_project.values() if row['latest_deletion']] return results else: return db.query(old_sql, limit=None)
_LATEST_INSTANCE_DATABASES = { 'nova', 'nova_cell0', }
[docs]@query def latest_instance_interaction(db, kvm, nova_db_name='nova'): ''' Get the latest interaction date with instances on the target database name. Combine as you so desire. ''' if nova_db_name not in _LATEST_INSTANCE_DATABASES: # can't parameterize a database name raise RuntimeError('invalid database selection') if kvm: table = '{nova_db_name}.instances'.format(nova_db_name=nova_db_name) first_col = 'project_id' second_col = 'project_id' else: table = '{nova_db_name}.instances AS inst INNER JOIN keystone.project AS proj ON inst.project_id = proj.id'.format(nova_db_name=nova_db_name) first_col = 'proj.name' second_col = 'proj.id' sql = '''\ SELECT {first_col} AS name, {second_col} AS id, MAX(IFNULL(deleted_at, IFNULL(updated_at, created_at))) AS latest_interaction, MAX(deleted_at is NULL) > 0 AS active FROM {table} GROUP BY project_id; '''.format(first_col=first_col, second_col=second_col,table=table) return db.query(sql, limit=None)
[docs]@query def owned_ips(db, project_ids): ''' Return all IPs associated with *project_ids* Maria 5.5 in production doesn't seem to like this, but works fine with a local MySQL 5.7. Is it Maria? 5.5? Too many? See owned_ip_single for one that works, but need to call multiple times. ''' sql = ''' SELECT id , status , {projcol} AS project_id FROM neutron.floatingips WHERE {projcol} IN %s; '''.format(projcol=project_col(db.version)) return db.query(sql, args=[project_ids], limit=None)
@query def floating_ips_to_leases(db, floating_ip_ids): """Return the leases for a tuple of floating ip ids.""" floating_ips_varargs = ','.join(['%s'] * len(floating_ip_ids)) sql = ''' SELECT bl.id AS lease_id , bl.end_date AS end_date , nfi.id AS ip_id FROM neutron.floatingips nfi LEFT JOIN neutron.ports np ON nfi.fixed_port_id=np.id LEFT JOIN nova.instances ni ON np.device_id=ni.uuid LEFT JOIN ironic.nodes ino ON ni.uuid=ino.instance_uuid LEFT JOIN blazar.computehosts bc ON ino.uuid = bc.hypervisor_hostname LEFT JOIN blazar.computehost_allocations bca ON bca.compute_host_id=bc.id LEFT JOIN blazar.reservations br ON bca.reservation_id=br.id LEFT JOIN blazar.leases bl ON br.lease_id=bl.id WHERE bl.project_id=nfi.project_id AND nfi.id IN ({floating_ips_varargs}); '''.format(floating_ips_varargs=floating_ips_varargs) return db.query(sql, args=floating_ip_ids, limit=None) @query def get_advanced_reservations_by_node_type(db): """Return all leases on GPUs ordered by node id and lease start date.""" sql = ''' SELECT klu.name AS user_name , ku.extra AS user_extra , bl.user_id AS user_id , bl.project_id AS project_id , bl.id AS lease_id , bl.start_date AS start_date , bl.end_date AS end_date , bce.capability_value AS node_type FROM blazar.leases bl RIGHT JOIN blazar.reservations br ON bl.id=br.lease_id RIGHT JOIN blazar.computehost_allocations bca ON br.id=bca.reservation_id RIGHT JOIN blazar.computehosts bc ON bca.compute_host_id=bc.id LEFT JOIN blazar.computehost_extra_capabilities bce ON bc.id=bce.computehost_id LEFT JOIN keystone.user ku ON bl.user_id=ku.id LEFT JOIN keystone.local_user klu ON ku.id=klu.user_id WHERE bl.start_date > CURDATE() AND bc.id=bce.computehost_id AND bce.capability_name='node_type' AND (bce.capability_value LIKE 'gpu%' OR bce.capability_value LIKE 'compute%' OR bce.capability_value LIKE 'storage%' OR bce.capability_value LIKE 'lowpower%' OR bce.capability_value LIKE 'fpga' OR bce.capability_value LIKE 'arm64' OR bce.capability_value LIKE 'atom') AND bl.deleted IS NULL ORDER BY node_type, start_date ASC; ''' return db.query(sql, limit=None) @query def owned_compute_ip_single(db, project_id): ''' Return all IPs associated with *project_id* and if associated with a port, whose fixed port is owned by compute ''' sql = ''' SELECT f.id , f.status , f.{projcol} AS project_id FROM neutron.floatingips AS f LEFT JOIN neutron.ports AS p ON f.fixed_port_id = p.id WHERE f.{projcol} = %s AND ( p.device_owner LIKE 'compute%%' OR p.id is NULL ); '''.format(projcol=project_col(db.version)) return db.query(sql, args=[project_id], limit=None) @query def projects_with_unowned_ports(db, version='liberty'): sql = ''' SELECT {projcol} AS project_id , count({projcol}) AS count_blank_owner FROM neutron.ports WHERE device_owner = '' GROUP BY {projcol}; '''.format(projcol=project_col(db.version)) return db.query(sql, limit=None) @query def owned_compute_port_single(db, project_id): ''' Return all ports associated with *project_id* and is owned by compute ''' sql = ''' SELECT id , status , {projcol} AS project_id FROM neutron.ports WHERE {projcol} = %s AND device_owner LIKE 'compute%%'; '''.format(projcol=project_col(db.version)) return db.query(sql, args=[project_id], limit=None)
[docs]@query def future_reservations(db): ''' Get project IDs with lease end dates in the future that haven't been deleted. This will also grab *active* leases, but that's erring on the safe side. ''' sql = ''' SELECT DISTINCT project_id FROM blazar.leases WHERE end_date > UTC_TIMESTAMP() AND deleted_at is NULL; ''' return db.query(sql, limit=None)
@query def active_instances(db): ''' Get active instances. ''' sql = ''' SELECT DISTINCT uuid, user_id, project_id FROM nova.instances WHERE deleted_at is NULL; ''' return db.query(sql, limit=None) @query def orphans(db, orphan_type): ''' Get orphans of certain type that haven't been deleted, along with users' and projects' information. ''' db_tables = [] conditions = '0' id_name = 'id' if orphan_type == 'lease': db_tables.append('blazar.leases') conditions = 'end_date > Now() AND deleted_at is NULL' elif orphan_type == 'instance': db_tables = [t + '.instances' for t in _LATEST_INSTANCE_DATABASES] conditions = 'deleted_at is NULL' id_name = 'uuid' else: raise RuntimeError('Orphan type of {} is not supported'.format(orphan_type)) projcol = project_col(db.version) result = [] for t in db_tables: sql = ''' SELECT m.id, m.user_id AS user_id, project_id, lu.name AS user_name, p.name AS project_name, u.enabled AS user_enabled, p.enabled AS project_enabled FROM ( SELECT {id_name} AS id, user_id, {projcol} AS project_id FROM {db_table_name} WHERE {conditions} ) AS m LEFT JOIN keystone.local_user AS lu ON lu.user_id = m.user_id LEFT JOIN keystone.user AS u ON u.id = m.user_id LEFT JOIN keystone.project AS p ON p.id = m.project_id WHERE m.user_id IS NULL OR project_id IS NULL OR u.enabled = 0 OR p.enabled = 0 OR ( u.enabled = 1 AND p.enabled = 1 AND NOT EXISTS ( SELECT 1 FROM keystone.assignment AS a WHERE type = 'UserProject' AND a.actor_id = m.user_id AND a.target_id = m.project_id ) ) '''.format(id_name=id_name, projcol=projcol, db_table_name=t, conditions=conditions) result += db.query(sql, limit=None) return result
[docs]@query def clear_ironic_port_internalinfo(db, port_id): """Remove internal_info data from ports. When the data wasn't cleaned up, it appeared to block other instances from spawning on the node. Now it may not be required? More research needed. """ sql = '''\ UPDATE ironic.ports SET internal_info = '{}' WHERE uuid = %s; ''' return db.query(sql, args=[port_id], no_rows=True)
[docs]@query def remove_extra_capability(db, host_id, capability_name): """ Remove an extra capability by name from `host_id`. (HTTP API doesn't support this as of Feb 2018) """ sql = '''\ DELETE FROM blazar.computehost_extra_capabilities WHERE computehost_id = %s AND capability_name = %s ''' return db.query(sql, args=[host_id, capability_name], no_rows=True)
@query def remove_extra_capability_sentinel(db, sentinel): """Remove all extra capabilities with value `sentinel`. The HTTP API could be used to mark them.""" sql = '''\ DELETE FROM blazar.computehost_extra_capabilities WHERE capability_name = %s ''' return db.query(sql, args=[sentinel], no_rows=True) @query def count_orphan_resource_providers(db): """Count all orphan resource providers in the nova_api database.""" sql = '''\ SELECT COUNT(*) FROM nova_api.resource_providers rp JOIN nova.compute_nodes cn ON cn.hypervisor_hostname = rp.name WHERE cn.deleted = 0 AND rp.uuid != cn.uuid ''' return db.query(sql) @query def update_orphan_resource_providers(db): """Update all orphan resource providers in the nova_api database.""" sql = '''\ UPDATE nova_api.resource_providers rp JOIN nova.compute_nodes cn ON cn.hypervisor_hostname = rp.name SET rp.uuid = cn.uuid WHERE cn.deleted = 0 AND rp.uuid != cn.uuid ''' return db.query(sql, no_rows=True) @query def get_nodes_by_lease(db, lease_id): """Get node ids of hosts in a lease.""" sql = '''\ SELECT ch.hypervisor_hostname AS node_id FROM blazar.leases AS l JOIN blazar.reservations AS r ON l.id = r.lease_id JOIN blazar.computehost_allocations AS ca ON ca.reservation_id = r.id JOIN blazar.computehosts AS ch ON ca.compute_host_id = ch.id WHERE l.id = %s ''' return db.query(sql, (lease_id,), limit=None) @query def get_advance_reservations(db): """Get all advance reservations created by any user""" sql = '''\ SELECT lu.name AS user_name , u.extra AS user_extra , l.name AS lease_name , l.id AS lease_id , p.name AS project_name , l.start_date AS start_date FROM blazar.leases AS l JOIN keystone.user AS u ON l.user_id = u.id JOIN keystone.local_user AS lu ON u.id = lu.user_id JOIN keystone.project AS p ON l.project_id = p.id WHERE l.start_date > UTC_TIMESTAMP() AND l.deleted_at IS NULL ''' return db.query(sql, limit=None) @query def get_idle_leases(db, hours): """ Get all leases that have been idle for more than a given number hours. An idle lease is defined as an ongoing lease without active instance and without lease update operation. """ sql = '''\ SELECT lu.name AS user_name, u.extra AS user_extra, s.lease_name AS lease_name, s.lease_id AS lease_id, p.name AS project_name FROM ( SELECT l.id AS lease_id, l.name AS lease_name, l.user_id, l.project_id FROM blazar.leases AS l JOIN blazar.reservations AS r ON l.id = r.lease_id JOIN blazar.computehost_allocations AS ca ON ca.reservation_id = r.id JOIN blazar.computehosts ch ON ca.compute_host_id = ch.id LEFT JOIN nova.instances AS i ON i.node = ch.hypervisor_hostname AND l.user_id = i.user_id AND l.project_id = i.project_id WHERE l.deleted_at IS NULL AND l.start_date < UTC_TIMESTAMP() AND l.end_date > UTC_TIMESTAMP() AND TIMESTAMPDIFF(HOUR, l.updated_at, UTC_TIMESTAMP()) >= %s AND TIMESTAMPDIFF(HOUR, r.updated_at, UTC_TIMESTAMP()) >= %s GROUP BY l.id HAVING COUNT(DISTINCT i.uuid) = 0 OR ( COUNT(DISTINCT i.uuid) > 0 AND SUM(ISNULL(i.deleted_at)) = 0 AND TIMESTAMPDIFF(HOUR, MAX(i.updated_at), UTC_TIMESTAMP()) >= %s ) ) AS s JOIN keystone.user AS u ON s.user_id = u.id JOIN keystone.local_user AS lu ON u.id = lu.user_id JOIN keystone.project AS p ON s.project_id = p.id ''' return db.query(sql, (hours, hours, hours), limit=None)
[docs]def main(argv): """Run queries!""" import sys import argparse import ast from .mysqlargs import MySqlArgs parser = argparse.ArgumentParser(description=main.__doc__) mysqlargs = MySqlArgs({ 'user': 'root', 'password': '', 'host': 'localhost', 'port': 3306, }) mysqlargs.inject(parser) parser.add_argument('query', type=str, choices=QUERIES, help='Query to run.', ) parser.add_argument('qargs', type=str, nargs='*', help='Arguments for the query (if needed)' ) parser.add_argument('--commit', action='store_true', help='Commit the connection after the query') args = parser.parse_args(argv[1:]) mysqlargs.extract(args) db = mysqlargs.connect() # qargs = [ast.literal_eval(a) for a in args.qargs] try: for row in QUERIES[args.query]['f'](db, *args.qargs): print(row) except TypeError as e: if '{}() takes'.format(args.query) in str(e): print('Invalid number of arguments provided to query: {}'.format(str(e)), file=sys.stderr) return -1 elif "'NoneType' object is not iterable" in str(e): print('No output from query.') else: raise if args.commit: db.db.commit()
if __name__ == '__main__': import sys sys.exit(main(sys.argv))