diff --git a/test/ci-bootstrap.py b/test/ci-bootstrap.py index 547ebdea57b835511c26ae859d3e3078c79946f4..1ab001c36838821da5163e04214e91234acb3fbb 100755 --- a/test/ci-bootstrap.py +++ b/test/ci-bootstrap.py @@ -42,7 +42,7 @@ import subprocess import sys import traceback import yaml -import cosmos +import greenhost_cloud SETTINGS_FILE = './group_vars/all/settings.yml' ANSIBLE_INVENTORY = './inventory.yml' @@ -115,9 +115,9 @@ def main(): # pylint: disable=too-many-statements,too-many-branches loglevel = logging.DEBUG if verbose else logging.INFO init_logging(log, loglevel) - # Setup logging for cosmos module - log_cosmos = logging.getLogger('cosmos') - init_logging(log_cosmos, loglevel) + # Setup logging for greenhost_cloud module + log_greenhost_cloud = logging.getLogger('greenhost_cloud') + init_logging(log_greenhost_cloud, loglevel) if not args.use_existing_inventory: # Start bootstrapping @@ -138,7 +138,7 @@ def main(): # pylint: disable=too-many-statements,too-many-branches random.choice(string.ascii_lowercase + string.digits) for _ in range(10)) - droplet = cosmos.create_droplet( + droplet = greenhost_cloud.create_droplet( name='ci-' + instance_id, ssh_key_id=args.ssh_key_id, region='ams1', @@ -147,15 +147,15 @@ def main(): # pylint: disable=too-many-statements,too-many-branches image=19) droplet_id = droplet['droplet']['id'] log.info('Created droplet id: %s', droplet_id) - cosmos.wait_for_state(droplet_id, 'running') + greenhost_cloud.wait_for_state(droplet_id, 'running') else: droplet_id = args.droplet_id if verbose: - cosmos.list_droplets() + greenhost_cloud.list_droplets() # Get droplet ip - droplet = cosmos.get_droplet(droplet_id) + droplet = greenhost_cloud.get_droplet(droplet_id) droplet_ip = droplet['networks']['v4'][0]['ip_address'] droplet_name = droplet['name'] @@ -197,7 +197,7 @@ def main(): # pylint: disable=too-many-statements,too-many-branches log.debug(yaml.safe_dump(settings, default_flow_style=False)) # Wait for ssh - cosmos.wait_for_ssh(droplet_ip) + greenhost_cloud.wait_for_ssh(droplet_ip) else: # Work with the master node from the inventory with open(ANSIBLE_INVENTORY, 'r') as stream: @@ -212,18 +212,18 @@ def main(): # pylint: disable=too-many-statements,too-many-branches if args.create_domain_records: # Create domain records - domain_record = cosmos.create_domain_record( + domain_record = greenhost_cloud.create_domain_record( domain='openappstack.net', name=droplet_name + '.ci', data=droplet_ip, record_type='A', update=True) log.info("Domain record: %s", domain_record) - domain_record = cosmos.create_domain_record( + domain_record = greenhost_cloud.create_domain_record( domain='openappstack.net', name='*.' + droplet_name + '.ci', data=droplet_name + '.ci', record_type='CNAME', update=True) log.info("Domain record: %s", domain_record) if verbose: - cosmos.list_domain_records('openappstack.net') + greenhost_cloud.list_domain_records('openappstack.net') if args.run_ansible: @@ -233,7 +233,7 @@ def main(): # pylint: disable=too-many-statements,too-many-branches write_behave_config(settings=settings) if args.terminate: - cosmos.terminate_droplets_by_name(droplet_name) + greenhost_cloud.terminate_droplets_by_name(droplet_name) def run_ansible(playbook, ansible_params): diff --git a/test/cosmos.py b/test/cosmos.py deleted file mode 100755 index 9655052f4b8a741c73d2877d4a70b5170f558774..0000000000000000000000000000000000000000 --- a/test/cosmos.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python3 -"""Python module with helper functions to use the cosmos API.""" - -import json -from datetime import datetime -from datetime import timedelta -import logging -import os -import re -import socket -from time import sleep - -import requests -from tabulate import tabulate -from pytz import timezone - -# Helper functions -def request_api(resource: str, request_type: str = 'GET', - data: str = ''): - """Query the cosmos API.""" - if 'COSMOS_API_TOKEN' in os.environ: - api_token = os.environ['COSMOS_API_TOKEN'] - else: - raise ValueError('Please export the COSMOS_API_TOKEN ' - 'environment variable.') - - headers = {'Content-Type': 'application/json', - 'Authorization': 'Bearer {0}'.format(api_token)} - api_url_base = 'https://service.greenhost.net/api/v2' - api_url = '{0}/{1}'.format(api_url_base, resource) - - if request_type == 'GET': - response = requests.get(api_url, headers=headers) - elif request_type == 'DELETE': - response = requests.delete(api_url, headers=headers) - elif request_type == 'POST': - response = requests.post( - api_url, headers=headers, data=json.dumps(data)) - elif request_type == 'PUT': - response = requests.put( - api_url, headers=headers, data=json.dumps(data)) - else: - raise ValueError('Specify one of GET/DELETE/POST/PUT as request_type.') - - log.debug('Request: %s, %s, data: %s', - response.url, request_type, data) - log.debug('Response code: %s', response.status_code) - - status_code_ok = [200, 201, 202, 204] - if response.status_code in status_code_ok: - if response.content: - log.debug('Response: %s\n', response.json()) - return json.loads(response.content.decode('utf-8')) - else: - return None - else: - raise requests.HTTPError('WARNING: Got response code ', - response.status_code, response.text) - - -# API calls -def create_domain_record(domain: str, name: str, data: str, - record_type: str = 'A', update: bool = False): - """Create domain record. - - If 'update' is set to True, the record will be updated if it exists. - """ - log.info('Creating domain record') - - record = { - 'name': name, - 'data': data, - 'type': record_type - } - # Check if record exists - existing_record = get_domain_record_by_name(domain=domain, name=name, - record_type=record_type) - if existing_record: - if update: - log.info('Domain record exists - Updating the record.') - response = request_api( - 'domains/%s/records/%s' % (domain, existing_record['id']), - 'PUT', record) - else: - raise ValueError('Domain record exists - Doing nothing,' - 'please use "update=True" to update existing' - 'records.') - else: - log.info('Creating new record.') - response = request_api('domains/%s/records/' % domain, 'POST', record) - - return response['domain_record'] - - -def create_droplet(name: str, ssh_key_id: int, region: str = 'ams1', - size: int = 2048, disk: int = 20, image: int = 18): - """Create a droplet. - - Required values: - - name (str): Name of the droplet - - ssh_key_id (int): ssh key id to add - - Optional values with their default values: - - image (str): 18 (Ubuntu 18.04 x64) - - region (str): 'ams1' (Amsterdam 1) - - size (int): 2048 (2GB RAM) - - disk (int): 20 (20GB disk space) - """ - log.info('Creating droplet') - - data = { - "name": name, - "region": region, - "size": size, - "disk": disk, - "image": image, - "ssh_keys": ssh_key_id - } - response = request_api('droplet', 'POST', data) - return response - - -def delete_domain_record(domain: str, id: int): - """Delete a domain record.""" - log.info('Deleting domain record %s', id) - response = request_api('domains/{0}/records/{1}'.format(domain, id), - 'DELETE') - return response - - -def delete_domain_records_by_name(domain: str, name_regex: str): - """Delete all domain records in a given domain matching a regex. - - Examples: - delete_domain_records_by_name('openappstack.net', '^\*.ci-') - delete_domain_records_by_name('openappstack.net', '^ci-') - """ - all = get_domain_records_by_name(domain, name_regex) - for record in all: - delete_domain_record(domain, record['id']) - - -def delete_droplet(id: int): - """Delete a droplet. Droplet needs to be stopped first.""" - log.info('Deleting %s', id) - response = request_api('droplets/{0}'.format(id), 'DELETE') - return response - - -def get_domain_record(domain: str, id: int): - """Get details for given domain record.""" - response = request_api('domains/{0}/records/{1}'.format(domain, id)) - return response['domain_record'] - - -def get_domain_records(domain: str): - """Get domain records for given domain.""" - response = request_api('domains/{0}/records'.format(domain)) - return response['domain_records'] - - -def get_domain_record_by_name(domain: str, name: str, - record_type: str = 'A'): - """ - Get domain record for given name and type. - - Example: - get_domain_record_by_name(domain='openappstack.net', name='varac-oas') - """ - records = get_domain_records(domain=domain) - matching = None - for record in records: - if record['name'] == name and record['type'] == record_type: - matching = record - break - if not matching: - log.info('No domain record found.') - - return matching - - -def get_domain_records_by_name(domain: str, name_regex: str): - r""" - Get all information about domain records matching a regex in their names. - - Example: - get_domain_records_by_name(name_regex='^ci\d+') - """ - all_records = get_domain_records(domain) - matching = [record for record in all_records - if re.match(name_regex, record['name'])] - return matching - - -def get_droplets(): - """Get all information about all droplets.""" - response = request_api('droplets') - return response['droplets'] - - -def get_droplets_by_name(name_regex: str): - r""" - Get all information about droplets matching a regex in their names. - - Example: - get_droplets_by_name(name_regex='^ci\d+') - """ - all_droplets = get_droplets() - matching = [droplet for droplet in all_droplets - if re.match('^ci+', droplet['name'])] - return matching - - -def get_droplet(id: int): - """Get information about specified droplet.""" - response = request_api('droplets/{0}'.format(id)) - return response['droplet'] - - -def list_domain_records(domain: str): - """List domain records for given domain.""" - records = get_domain_records(domain) - - log.debug(json.dumps(records, sort_keys=True, indent=2)) - - table_records = [[ - record['id'], record['name'], record['type'], record['data']] - for record in records] - log.info(tabulate(table_records, - headers=['ID', 'Name', 'Type', 'Data'])) - - -def list_droplets(): - """List all droplets by their ID, Name, IP and state.""" - droplets = get_droplets() - - log.debug(json.dumps(droplets, sort_keys=True, indent=2)) - - table_droplets = [ - [ - droplet['id'], - droplet['name'], - ', '.join([x['ip_address'] for x in droplet['networks']['v4']]), - droplet['status'] - ] - for droplet in droplets] - - log.info(tabulate(table_droplets, - headers=['ID', 'Name', 'IPv4', 'Status'])) - - -def shutdown_droplet(id: int): - """Shut down specified droplet (through a power_off call).""" - log.info('Shutting down %s', id) - data = {"type": "power_off"} - response = request_api('droplets/{0}/actions'.format(id), 'POST', data) - return response - - -def status_droplet(id: int): - """Get status of specified droplet.""" - response = get_droplet(id) - return response['status'] - - -def terminate_droplet(id: int): - """Terminate a droplet by powering it down and deleting it.""" - shutdown_droplet(id) - wait_for_state(id, 'stopped') - delete_droplet(id) - - -def terminate_droplets_by_name(name_regex: str, ndays: int = 0, domain: str = 'openappstack.net'): - r""" - Terminate droplets matching a regex and for x days older than current day. - Droplets defined on the env variable NO_TERMINATE_DROPLETS will not be delated - - Example how to terminate all CI instances: - terminate_old_droplets(name_regex='^ci\d+', ndays=5) - will match i.e 'ci1234' , 'ci1', with a creation time older than 5 days - """ - - threshold_time = (datetime.now(tz=timezone('Europe/Stockholm')) - timedelta(days=ndays)).strftime("%Y-%m-%dT%H:%M:%S+00:00") - all = get_droplets() - - noterminate_droplets = [] - if 'NO_TERMINATE_DROPLETS' in os.environ: - noterminate_droplets = os.environ['NO_TERMINATE_DROPLETS'].split(',') - - for droplet in all: - if droplet['name'] not in noterminate_droplets: - if re.match(name_regex, droplet['name']): - if droplet['created_at'] < threshold_time: - delete_domain_records_by_name(domain, '^\*.'+droplet['name']) - delete_domain_records_by_name(domain, '^'+droplet['name']) - terminate_droplet(droplet['id']) - -def wait_for_ssh(ip: str): - """Wait for ssh to be reachable on port 22.""" - log.info('Waiting for ssh to become available on ip %s', ip) - - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - while sock.connect_ex((ip, 22)) != 0: - sleep(1) - - -def wait_for_state(id: int, state): - """Wait for a droplet to reach a certain state.""" - log.info('Waiting for droplet %s to reach %s state...', id, state) - status = status_droplet(id) - log.debug(status) - - while status != state: - sleep(1) - status = status_droplet(id) - - -# When called from from ipython, setup -# logging to console -try: - __IPYTHON__ - log = logging.getLogger() - log.addHandler(logging.StreamHandler()) - log.setLevel(logging.INFO) -except NameError: - log = logging.getLogger(__name__) diff --git a/test/requirements.txt b/test/requirements.txt index 480ce481e2e2b8ef90e6394397a233592401592d..e52588ecc58589dec2786fb48c36b00936315683 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -15,4 +15,4 @@ testinfra>=2.0.0 setuptools>=40.6.2 wheel>=0.33.1 pytz>=2019.1 - +-e git://open.greenhost.net/greenhost/cosmos-api@5fc6125d3cf481bca3639650c7d1983e6db44621#egg=greenhost_cloud