#!/usr/bin/env python # Copyright 2014, Rackspace US, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # (c) 2014, Kevin Carter import argparse import json import os import netaddr PREFIX_NAME = 'RPC' SNAT_POOL = ( 'create ltm snatpool %(prefix_name)s_SNATPOOL { members replace-all-with {' ' %(snat_pool_addresses)s } }' ) MONITORS = ( 'create ltm monitor mysql %(prefix_name)s_MON_GALERA { count 0 database' ' information_schema debug yes defaults-from mysql destination *:*' ' interval 30 time-until-up 0 timeout 91 username haproxy }' ) NODES = ( 'create ltm node %(node_name)s { address %(container_address)s }' ) PRIORITY_ENTRY = '{ priority-group %(priority_int)s }' POOL_NODE = { 'beginning': 'create ltm pool %(pool_name)s {' ' load-balancing-mode fastest-node members replace-all-with' ' { %(nodes)s }', 'priority': 'min-active-members 1', 'end': 'monitor %(mon_type)s }' } VIRTUAL_ENTRIES = ( 'create ltm virtual %(vs_name)s {' ' destination %(internal_lb_vip_address)s:%(port)s' ' ip-protocol tcp mask 255.255.255.255' ' pool %(pool_name)s profiles replace-all-with { fastL4 { } }' ' source 0.0.0.0/0 source-address-translation {' ' pool RPC_SNATPOOL type snat } }' ) # This is a dict of all groups and their respected values / requirements POOL_PARTS = { 'galera': { 'port': 3306, 'backend_port': 3306, 'mon_type': 'RPC_MON_GALERA', 'priority': True, 'group': 'galera', 'hosts': [] }, 'glance_api': { 'port': 9292, 'backend_port': 9292, 'mon_type': 'http', 'group': 'glance_api', 'hosts': [] }, 'glance_registry': { 'port': 9191, 'backend_port': 9191, 'mon_type': 'http', 'group': 'glance_registry', 'hosts': [] }, 'heat_api_cfn': { 'port': 8000, 'backend_port': 8000, 'mon_type': 'http', 'group': 'heat_api_cfn', 'hosts': [] }, 'heat_api_cloudwatch': { 'port': 8003, 'backend_port': 8003, 'mon_type': 'http', 'group': 'heat_api_cloudwatch', 'hosts': [] }, 'heat_api': { 'port': 8004, 'backend_port': 8004, 'mon_type': 'http', 'group': 'heat_api', 'hosts': [] }, 'keystone_admin': { 'port': 35357, 'backend_port': 35357, 'mon_type': 'http', 'group': 'keystone', 'hosts': [] }, 'keystone_service': { 'port': 5000, 'backend_port': 5000, 'mon_type': 'http', 'group': 'keystone', 'hosts': [] }, 'neutron_server': { 'port': 9696, 'backend_port': 9696, 'mon_type': 'http', 'group': 'neutron_server', 'hosts': [] }, 'nova_api_ec2': { 'port': 8773, 'backend_port': 8773, 'mon_type': 'http', 'group': 'nova_api_ec2', 'hosts': [] }, 'nova_api_metadata': { 'port': 8775, 'backend_port': 8775, 'mon_type': 'http', 'group': 'nova_api_metadata', 'hosts': [] }, 'nova_api_os_compute': { 'port': 8774, 'backend_port': 8774, 'mon_type': 'http', 'group': 'nova_api_os_compute', 'hosts': [] }, 'nova_spice_console': { 'port': 6082, 'backend_port': 6082, 'mon_type': 'http', 'group': 'nova_spice_console', 'hosts': [] }, 'cinder_api': { 'port': 8776, 'backend_port': 8776, 'mon_type': 'http', 'group': 'cinder_api', 'hosts': [] }, 'horizon': { 'port': 80, 'backend_port': 80, 'mon_type': 'http', 'group': 'horizon', 'hosts': [] }, 'horizon_ssl': { 'port': 443, 'backend_port': 443, 'mon_type': 'tcp', 'group': 'horizon', 'hosts': [] }, 'memcached': { 'port': 11211, 'backend_port': 11211, 'mon_type': 'tcp', 'group': 'memcached', 'priority': True, 'limit_source': True, 'hosts': [] }, 'elasticsearch': { 'port': 9200, 'backend_port': 9200, 'mon_type': 'tcp', 'group': 'elasticsearch', 'hosts': [] }, 'kibana': { 'port': 8080, 'backend_port': 80, 'mon_type': 'http', 'group': 'kibana', 'priority': True, 'hosts': [] }, 'kibana_ssl': { 'port': 8443, 'backend_port': 443, 'mon_type': 'tcp', 'group': 'kibana', 'priority': True, 'hosts': [] } } def recursive_host_get(inventory, group_name, host_dict=None): if host_dict is None: host_dict = {} inventory_group = inventory.get(group_name) if 'children' in inventory_group and inventory_group['children']: for child in inventory_group['children']: recursive_host_get( inventory=inventory, group_name=child, host_dict=host_dict ) if inventory_group.get('hosts'): for host in inventory_group['hosts']: if host not in host_dict['hosts']: ca = inventory['_meta']['hostvars'][host]['container_address'] node = { 'hostname': host, 'container_address': ca } host_dict['hosts'].append(node) return host_dict def build_pool_parts(inventory): for key, value in POOL_PARTS.iteritems(): recursive_host_get( inventory, group_name=value['group'], host_dict=value ) return POOL_PARTS def file_find(filename, user_file=None, pass_exception=False): """Return the path to a file. If no file is found the system will exit. The file lookup will be done in the following directories: /etc/rpc_deploy/ $HOME/rpc_deploy/ $(pwd)/rpc_deploy/ :param filename: ``str`` Name of the file to find :param user_file: ``str`` Additional localtion to look in FIRST for a file """ file_check = [ os.path.join( '/etc', 'rpc_deploy', filename ), os.path.join( os.environ.get('HOME'), 'rpc_deploy', filename ), os.path.join( os.getcwd(), filename ) ] if user_file is not None: file_check.insert(0, os.path.expanduser(user_file)) for f in file_check: if os.path.isfile(f): return f else: if pass_exception is False: raise SystemExit('No file found at: %s' % file_check) else: return False def args(): """Setup argument Parsing.""" parser = argparse.ArgumentParser( usage='%(prog)s', description='Rackspace Openstack, Inventory Generator', epilog='Inventory Generator Licensed "Apache 2.0"') parser.add_argument( '-f', '--file', help='Inventory file. Default: [ %(default)s ]', required=False, default='rpc_inventory.json' ) parser.add_argument( '-s', '--snat-pool-address', help='LB Main SNAT pool address for [ RPC_SNATPOOL ], for' ' multiple snat pool addresses comma seperate the ip' ' addresses. By default this IP will be .15 from within your' ' containers_cidr as found within inventory.', required=False, default=None ) parser.add_argument( '--limit-source', help='Limit available connections to the source IP for all source' ' limited entries.', required=False, default=None ) parser.add_argument( '-e', '--export', help='Export the generated F5 configuration script.' ' Default: [ %(default)s ]', required=False, default=os.path.join( os.path.expanduser('~/'), 'rpc_f5_config.sh' ) ) return vars(parser.parse_args()) def main(): """Run the main application.""" # Parse user args user_args = args() # Get the contents of the system environment json environment_file = file_find(filename=user_args['file']) with open(environment_file, 'rb') as f: inventory_json = json.loads(f.read()) nodes = [] pools = [] virts = [] pool_parts = build_pool_parts(inventory=inventory_json) lb_vip_address = inventory_json['all']['vars']['internal_lb_vip_address'] for key, value in pool_parts.iteritems(): value['group_name'] = key.upper() value['vs_name'] = '%s_VS_%s' % ( PREFIX_NAME, value['group_name'] ) value['pool_name'] = '%s_POOL_%s' % ( PREFIX_NAME, value['group_name'] ) node_data = [] priority = 100 for node in value['hosts']: node['node_name'] = '%s_NODE_%s' % (PREFIX_NAME, node['hostname']) nodes.append('%s\n' % NODES % node) virt = ( '%s\n' % VIRTUAL_ENTRIES % { 'port': value['port'], 'vs_name': value['vs_name'], 'pool_name': value['pool_name'], 'internal_lb_vip_address': lb_vip_address } ) if virt not in virts: virts.append(virt) if value.get('priority') is True: node_data.append( '%s:%s %s' % ( node['node_name'], value['backend_port'], PRIORITY_ENTRY % {'priority_int': priority} ) ) priority -= 5 else: node_data.append( '%s:%s' % ( node['node_name'], value['backend_port'] ) ) value['nodes'] = ' '.join(node_data) pool_node = [POOL_NODE['beginning'] % value] if value.get('priority') is True: pool_node.append(POOL_NODE['priority']) pool_node.append(POOL_NODE['end'] % value) pools.append('%s\n' % ' '.join(pool_node)) # define the SNAT pool address snat_pool_adds = user_args.get('snat_pool_address') if snat_pool_adds is None: container_cidr = inventory_json['all']['vars']['container_cidr'] network = netaddr.IPNetwork(container_cidr) snat_pool_adds = str(network[15]) snat_pool_addresses = ' '.join(snat_pool_adds.split(',')) snat_pool = '%s\n' % SNAT_POOL % { 'prefix_name': PREFIX_NAME, 'snat_pool_addresses': snat_pool_addresses } script = [ '#!/usr/bin/bash\n', snat_pool, '%s\n' % MONITORS % {'prefix_name': PREFIX_NAME} ] script.extend(nodes) script.extend(pools) script.extend(virts) with open(user_args['export'], 'wb') as f: f.writelines(script) if __name__ == "__main__": main()