pvc/client-provisioner/pvc-metadata.py

203 lines
7.3 KiB
Python
Executable File

#!/usr/bin/env python3
# pvc-provisioner.py - PVC Provisioner API interface
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018-2019 Joshua M. Boniface <joshua@boniface.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
import flask
import json
import yaml
import os
import sys
import uu
import distutils.util
import gevent.pywsgi
import provisioner_lib.provisioner as pvc_provisioner
import client_lib.common as pvc_common
import client_lib.vm as pvc_vm
import client_lib.network as pvc_network
# Parse the configuration file
try:
pvc_config_file = os.environ['PVC_CONFIG_FILE']
except:
print('Error: The "PVC_CONFIG_FILE" environment variable must be set before starting pvc-provisioner.')
exit(1)
print('Starting PVC Provisioner Metadata API daemon')
# Read in the config
try:
with open(pvc_config_file, 'r') as cfgfile:
o_config = yaml.load(cfgfile)
except Exception as e:
print('Failed to parse configuration file: {}'.format(e))
exit(1)
try:
# Create the config object
config = {
'debug': o_config['pvc']['debug'],
'coordinators': o_config['pvc']['coordinators'],
'listen_address': o_config['pvc']['provisioner']['listen_address'],
'listen_port': int(o_config['pvc']['provisioner']['listen_port']),
'auth_enabled': o_config['pvc']['provisioner']['authentication']['enabled'],
'auth_secret_key': o_config['pvc']['provisioner']['authentication']['secret_key'],
'auth_tokens': o_config['pvc']['provisioner']['authentication']['tokens'],
'ssl_enabled': o_config['pvc']['provisioner']['ssl']['enabled'],
'ssl_key_file': o_config['pvc']['provisioner']['ssl']['key_file'],
'ssl_cert_file': o_config['pvc']['provisioner']['ssl']['cert_file'],
'database_host': o_config['pvc']['provisioner']['database']['host'],
'database_port': int(o_config['pvc']['provisioner']['database']['port']),
'database_name': o_config['pvc']['provisioner']['database']['name'],
'database_user': o_config['pvc']['provisioner']['database']['user'],
'database_password': o_config['pvc']['provisioner']['database']['pass'],
'queue_host': o_config['pvc']['provisioner']['queue']['host'],
'queue_port': o_config['pvc']['provisioner']['queue']['port'],
'queue_path': o_config['pvc']['provisioner']['queue']['path'],
'storage_hosts': o_config['pvc']['cluster']['storage_hosts'],
'storage_domain': o_config['pvc']['cluster']['storage_domain'],
'ceph_monitor_port': o_config['pvc']['cluster']['ceph_monitor_port'],
'ceph_storage_secret_uuid': o_config['pvc']['cluster']['ceph_storage_secret_uuid']
}
if not config['storage_hosts']:
config['storage_hosts'] = config['coordinators']
# Set the config object in the pvcapi namespace
pvc_provisioner.config = config
except Exception as e:
print('{}'.format(e))
exit(1)
# Get our listening address from the CLI
router_address = sys.argv[1]
# Try to connect to the database or fail
try:
print('Verifying connectivity to database')
conn, cur = pvc_provisioner.open_database(config)
pvc_provisioner.close_database(conn, cur)
except Exception as e:
print('{}'.format(e))
exit(1)
api = flask.Flask(__name__)
if config['debug']:
api.config['DEBUG'] = True
if config['auth_enabled']:
api.config["SECRET_KEY"] = config['auth_secret_key']
print(api.name)
def get_vm_details(source_address):
# Start connection to Zookeeper
zk_conn = pvc_common.startZKConnection(config['coordinators'])
_discard, networks = pvc_network.get_list(zk_conn, None)
# Figure out which server this is via the DHCP address
host_information = dict()
networks_managed = (x for x in networks if x['type'] == 'managed')
for network in networks_managed:
network_leases = pvc_network.getNetworkDHCPLeases(zk_conn, network['vni'])
for network_lease in network_leases:
information = pvc_network.getDHCPLeaseInformation(zk_conn, network['vni'], network_lease)
try:
if information['ip4_address'] == source_address:
host_information = information
except:
pass
# Get our real information on the host; now we can start querying about it
client_hostname = host_information['hostname']
client_macaddr = host_information['mac_address']
client_ipaddr = host_information['ip4_address']
# Find the VM with that MAC address - we can't assume that the hostname is actually right
_discard, vm_list = pvc_vm.get_list(zk_conn, None, None, None)
vm_name = None
vm_details = dict()
for vm in vm_list:
try:
for network in vm['networks']:
if network['mac'] == client_macaddr:
vm_name = vm['name']
vm_details = vm
except:
pass
# Stop connection to Zookeeper
pvc_common.stopZKConnection(zk_conn)
return vm_details
@api.route('/', methods=['GET'])
def api_root():
return flask.jsonify({"message": "PVC Provisioner Metadata API version 1"}), 209
@api.route('/<version>/meta-data/', methods=['GET'])
def api_metadata_root(version):
metadata = """instance-id"""
return metadata, 200
@api.route('/<version>/meta-data/instance-id', methods=['GET'])
def api_metadata_instanceid(version):
# router_address = flask.request.__dict__['environ']['SERVER_NAME']
source_address = flask.request.__dict__['environ']['REMOTE_ADDR']
vm_details = get_vm_details(source_address)
instance_id = vm_details['uuid']
return instance_id, 200
@api.route('/<version>/user-data', methods=['GET'])
def api_userdata(version):
source_address = flask.request.__dict__['environ']['REMOTE_ADDR']
vm_details = get_vm_details(source_address)
vm_profile = vm_details['profile']
print("Profile: {}".format(vm_profile))
# Get profile details
profile_details = pvc_provisioner.list_profile(vm_profile, is_fuzzy=False)[0]
# Get the userdata
userdata = pvc_provisioner.list_template_userdata(profile_details['userdata_template'])[0]['userdata']
print(userdata)
return flask.Response(userdata, mimetype='text/cloud-config')
#
# Entrypoint
#
if __name__ == '__main__':
# Start main API
if config['debug']:
# Run in Flask standard mode
api.run('169.254.169.254', 80)
else:
# Run the PYWSGI serve
http_server = gevent.pywsgi.WSGIServer(
('10.200.0.1', 80),
api
)
print('Starting PyWSGI server at {}:{}'.format('169.254.169.254', 80))
http_server.serve_forever()