| #!/usr/bin/env python |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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. |
| |
| from __future__ import with_statement |
| |
| import os |
| import sys |
| import inspect |
| from collections import OrderedDict |
| from os.path import join as pjoin |
| |
| this_dir = os.path.abspath(os.path.split(__file__)[0]) |
| sys.path.insert(0, os.path.join(this_dir, '../')) |
| |
| from libcloud.compute .base import NodeDriver |
| from libcloud.compute.providers import get_driver as get_compute_driver |
| from libcloud.compute.providers import DRIVERS as COMPUTE_DRIVERS |
| from libcloud.compute.types import Provider as ComputeProvider |
| |
| from libcloud.loadbalancer.base import Driver as LBDriver |
| from libcloud.loadbalancer.providers import get_driver as get_lb_driver |
| from libcloud.loadbalancer.providers import DRIVERS as LB_DRIVERS |
| from libcloud.loadbalancer.types import Provider as LBProvider |
| |
| from libcloud.storage.base import StorageDriver |
| from libcloud.storage.providers import get_driver as get_storage_driver |
| from libcloud.storage.providers import DRIVERS as STORAGE_DRIVERS |
| from libcloud.storage.types import Provider as StorageProvider |
| |
| from libcloud.dns.base import DNSDriver |
| from libcloud.dns.providers import get_driver as get_dns_driver |
| from libcloud.dns.providers import DRIVERS as DNS_DRIVERS |
| from libcloud.dns.types import Provider as DNSProvider |
| |
| from libcloud.container.base import ContainerDriver |
| from libcloud.container.providers import get_driver as get_container_driver |
| from libcloud.container.providers import DRIVERS as CONTAINER_DRIVERS |
| from libcloud.container.types import Provider as ContainerProvider |
| |
| from libcloud.backup.base import BackupDriver |
| from libcloud.backup.providers import get_driver as get_backup_driver |
| from libcloud.backup.providers import DRIVERS as BACKUP_DRIVERS |
| from libcloud.backup.types import Provider as BackupProvider |
| |
| |
| HEADER = ('.. NOTE: This file has been generated automatically using ' |
| 'generate_provider_feature_matrix_table.py script, don\'t manually ' |
| 'edit it') |
| |
| BASE_API_METHODS = { |
| 'compute_main': ['list_nodes', 'create_node', 'reboot_node', |
| 'destroy_node', 'start_node', 'stop_node', |
| 'list_images', 'list_sizes', 'deploy_node'], |
| 'compute_image_management': ['list_images', 'get_image', |
| 'create_image', 'delete_image', 'copy_image'], |
| 'compute_block_storage': ['list_volumes', 'create_volume', |
| 'destroy_volume', |
| 'attach_volume', 'detach_volume', |
| 'list_volume_snapshots', |
| 'create_volume_snapshot'], |
| 'compute_key_pair_management': ['list_key_pairs', 'get_key_pair', |
| 'create_key_pair', |
| 'import_key_pair_from_string', |
| 'import_key_pair_from_file', |
| 'delete_key_pair'], |
| 'loadbalancer': ['create_balancer', 'list_balancers', |
| 'balancer_list_members', 'balancer_attach_member', |
| 'balancer_detach_member', 'balancer_attach_compute_node'], |
| 'storage_main': ['list_containers', 'list_container_objects', |
| 'iterate_containers', 'iterate_container_objects', |
| 'create_container', 'delete_container', 'upload_object', |
| 'upload_object_via_stream', 'download_object', |
| 'download_object_as_stream', 'delete_object'], |
| 'storage_cdn': ['enable_container_cdn', 'enable_object_cdn', |
| 'get_container_cdn_url', 'get_object_cdn_url'], |
| 'dns': ['list_zones', 'list_records', 'iterate_zones', 'iterate_records', |
| 'create_zone', 'update_zone', 'create_record', 'update_record', |
| 'delete_zone', 'delete_record'], |
| 'container': ['install_image', 'list_images', 'deploy_container', |
| 'get_container', 'start_container', 'stop_container', |
| 'restart_container', 'destroy_container', 'list_containers', |
| 'list_locations', 'create_cluster', 'destroy_cluster', |
| 'list_clusters'], |
| 'backup': ['get_supported_target_types', 'list_targets', 'create_target', 'create_target_from_node', |
| 'create_target_from_storage_container', 'update_target', 'delete_target', 'list_recovery_points', |
| 'recover_target', 'recover_target_out_of_place', 'list_target_jobs', 'create_target_job', |
| 'resume_target_job', 'suspend_target_job', 'cancel_target_job'] |
| } |
| |
| FRIENDLY_METHODS_NAMES = { |
| 'compute_main': { |
| 'list_nodes': 'list nodes', |
| 'create_node': 'create node', |
| 'reboot_node': 'reboot node', |
| 'start_node': 'start node', |
| 'stop_node': 'stop node', |
| 'destroy_node': 'destroy node', |
| 'list_images': 'list images', |
| 'list_sizes': 'list sizes', |
| 'deploy_node': 'deploy node' |
| }, |
| 'compute_image_management': { |
| 'list_images': 'list images', |
| 'get_image': 'get image', |
| 'create_image': 'create image', |
| 'copy_image': 'copy image', |
| 'delete_image': 'delete image' |
| }, |
| 'compute_block_storage': { |
| 'list_volumes': 'list volumes', |
| 'create_volume': 'create volume', |
| 'destroy_volume': 'destroy volume', |
| 'attach_volume': 'attach volume', |
| 'detach_volume': 'detach volume', |
| 'list_volume_snapshots': 'list snapshots', |
| 'create_volume_snapshot': 'create snapshot' |
| }, |
| 'compute_key_pair_management': { |
| 'list_key_pairs': 'list key pairs', |
| 'get_key_pair': 'get key pair', |
| 'create_key_pair': 'create key pair', |
| 'import_key_pair_from_string': 'import public key from string', |
| 'import_key_pair_from_file': 'import public key from file', |
| 'delete_key_pair': 'delete key pair' |
| }, |
| 'loadbalancer': { |
| 'create_balancer': 'create balancer', |
| 'list_balancers': 'list balancers', |
| 'balancer_list_members': 'list members', |
| 'balancer_attach_member': 'attach member', |
| 'balancer_detach_member': 'detach member', |
| 'balancer_attach_compute_node': 'attach compute node' |
| }, |
| 'storage_main': { |
| 'list_containers': 'list containers', |
| 'list_container_objects': 'list objects', |
| 'create_container': 'create container', |
| 'delete_container': 'delete container', |
| 'upload_object': 'upload object', |
| 'upload_object_via_stream': 'streaming object upload', |
| 'download_object': 'download object', |
| 'download_object_as_stream': 'streaming object download', |
| 'delete_object': 'delete object' |
| }, |
| 'storage_cdn': { |
| 'enable_container_cdn': 'enable container cdn', |
| 'enable_object_cdn': 'enable object cdn', |
| 'get_container_cdn_url': 'get container cdn URL', |
| 'get_object_cdn_url': 'get object cdn URL', |
| }, |
| 'dns': { |
| 'list_zones': 'list zones', |
| 'list_records': 'list records', |
| 'create_zone': 'create zone', |
| 'update_zone': 'update zone', |
| 'create_record': 'create record', |
| 'update_record': 'update record', |
| 'delete_zone': 'delete zone', |
| 'delete_record': 'delete record' |
| }, |
| 'container': { |
| 'install_image': 'install image', |
| 'list_images': 'list images', |
| 'deploy_container': 'deploy container', |
| 'get_container': 'get container', |
| 'list_containers': 'list containers', |
| 'start_container': 'start container', |
| 'stop_container': 'stop container', |
| 'restart_container': 'restart container', |
| 'destroy_container': 'destroy container', |
| 'list_locations': 'list locations', |
| 'create_cluster': 'create cluster', |
| 'destroy_cluster': 'destroy cluster', |
| 'list_clusters': 'list clusters' |
| }, |
| 'backup': { |
| 'get_supported_target_types': 'get supported target types', |
| 'list_targets': 'list targets', |
| 'create_target': 'create target', |
| 'create_target_from_node': 'create target from node', |
| 'create_target_from_storage_container': 'create target from storage container', |
| 'update_target': 'update target', |
| 'delete_target': 'delete target', |
| 'list_recovery_points': 'list recovery points', |
| 'recover_target': 'recover target', |
| 'recover_target_out_of_place': 'recover target out of place', |
| 'list_target_jobs': 'list target jobs', |
| 'create_target_job': 'create target job', |
| 'resume_target_job': 'resume target job', |
| 'suspend_target_job': 'suspend target job', |
| 'cancel_target_job': 'cancel target job' |
| } |
| } |
| |
| IGNORED_PROVIDERS = [ |
| 'dummy', |
| |
| # Deprecated constants |
| 'cloudsigma_us', |
| 'cloudfiles_swift' |
| ] |
| |
| |
| def get_provider_api_names(Provider): |
| names = [key for key, value in Provider.__dict__.items() if |
| not key.startswith('__')] |
| return names |
| |
| |
| def generate_providers_table(api): |
| result = {} |
| |
| if api in ['compute_main', 'compute_image_management', |
| 'compute_block_storage', 'compute_key_pair_management']: |
| driver = NodeDriver |
| drivers = COMPUTE_DRIVERS |
| provider = ComputeProvider |
| get_driver_method = get_compute_driver |
| elif api == 'loadbalancer': |
| driver = LBDriver |
| drivers = LB_DRIVERS |
| provider = LBProvider |
| get_driver_method = get_lb_driver |
| elif api in ['storage_main', 'storage_cdn']: |
| driver = StorageDriver |
| drivers = STORAGE_DRIVERS |
| provider = StorageProvider |
| get_driver_method = get_storage_driver |
| elif api == 'dns': |
| driver = DNSDriver |
| drivers = DNS_DRIVERS |
| provider = DNSProvider |
| get_driver_method = get_dns_driver |
| elif api == 'container': |
| driver = ContainerDriver |
| drivers = CONTAINER_DRIVERS |
| provider = ContainerProvider |
| get_driver_method = get_container_driver |
| elif api == 'backup': |
| driver = BackupDriver |
| drivers = BACKUP_DRIVERS |
| provider = BackupProvider |
| get_driver_method = get_backup_driver |
| else: |
| raise Exception('Invalid api: %s' % (api)) |
| |
| names = get_provider_api_names(provider) |
| |
| result = OrderedDict() |
| for name in names: |
| enum = getattr(provider, name) |
| |
| try: |
| cls = get_driver_method(enum) |
| except Exception as e: |
| # Deprecated providers throw an exception |
| print('Ignoring deprecated constant "%s": %s' % (enum, str(e))) |
| continue |
| |
| # Hack for providers which expose multiple classes and support multiple |
| # API versions |
| # TODO: Make entry per version |
| if name.lower() == 'cloudsigma': |
| from libcloud.compute.drivers.cloudsigma import \ |
| CloudSigma_2_0_NodeDriver |
| cls = CloudSigma_2_0_NodeDriver |
| elif name.lower() == 'opennebula': |
| from libcloud.compute.drivers.opennebula import \ |
| OpenNebula_3_8_NodeDriver |
| cls = OpenNebula_3_8_NodeDriver |
| elif name.lower() == 'digital_ocean' and api.startswith('compute'): |
| from libcloud.compute.drivers.digitalocean import \ |
| DigitalOcean_v2_NodeDriver |
| cls = DigitalOcean_v2_NodeDriver |
| |
| if name.lower() in IGNORED_PROVIDERS: |
| continue |
| |
| def is_function_or_method(*args, **kwargs): |
| return (inspect.isfunction(*args, **kwargs) or |
| inspect.ismethod(*args, **kwargs)) |
| |
| driver_methods = dict(inspect.getmembers(cls, |
| predicate=is_function_or_method)) |
| base_methods = dict(inspect.getmembers(driver, |
| predicate=is_function_or_method)) |
| base_api_methods = BASE_API_METHODS[api] |
| |
| result[name] = {'name': cls.name, 'website': cls.website, |
| 'constant': name, 'module': drivers[enum][0], |
| 'class': drivers[enum][1], |
| 'cls': cls, |
| 'methods': {}} |
| |
| print('Generating tables for provider: %s' % (name)) |
| |
| for method_name in base_api_methods: |
| base_method = base_methods[method_name] |
| |
| if method_name == 'deploy_node': |
| features = getattr(cls, 'features', {}).get('create_node', []) |
| is_implemented = len(features) >= 1 |
| else: |
| if method_name not in driver_methods: |
| is_implemented = False |
| else: |
| driver_method = driver_methods[method_name] |
| # NOTE: id() is not safe |
| # is_implemented = (id(driver_method) != id(base_method)) |
| is_implemented = (driver_method != base_method) |
| |
| result[name]['methods'][method_name] = is_implemented |
| |
| return result |
| |
| |
| def generate_rst_table(data): |
| cols = len(data[0]) |
| col_len = [max(len(r[i]) for r in data) for i in range(cols)] |
| formatter = ' '.join('{:<%d}' % c for c in col_len) |
| |
| header = formatter.format(*['=' * c for c in col_len]) |
| rows = [formatter.format(*row) for row in data] |
| result = header + '\n' + rows[0] + '\n' + header + '\n' +\ |
| '\n'.join(rows[1:]) + '\n' + header |
| |
| return result |
| |
| |
| def generate_supported_methods_table(api, provider_matrix): |
| base_api_methods = BASE_API_METHODS[api] |
| data = [] |
| header = [FRIENDLY_METHODS_NAMES[api][method_name] for method_name in |
| base_api_methods if not method_name.startswith('iterate_')] |
| data.append(['Provider'] + header) |
| |
| for provider, values in sorted(provider_matrix.items()): |
| provider_name = '`%s`_' % (values['name']) |
| row = [provider_name] |
| |
| # TODO: Make it nicer |
| # list_* methods don't need to be implemented if iterate_* methods are |
| # implemented |
| if api == 'storage_main': |
| if values['methods']['iterate_containers']: |
| values['methods']['list_containers'] = True |
| |
| if values['methods']['iterate_container_objects']: |
| values['methods']['list_container_objects'] = True |
| elif api == 'dns': |
| # list_zones and list_records don't need to be implemented if |
| if values['methods']['iterate_zones']: |
| values['methods']['list_zones'] = True |
| |
| if values['methods']['iterate_records']: |
| values['methods']['list_records'] = True |
| |
| for method in base_api_methods: |
| # TODO: ghetto |
| if method.startswith('iterate_'): |
| continue |
| |
| supported = values['methods'][method] |
| |
| if supported: |
| row.append('yes') |
| else: |
| row.append('no') |
| data.append(row) |
| |
| result = generate_rst_table(data) |
| |
| result += '\n\n' |
| for provider, values in sorted(provider_matrix.items()): |
| result += '.. _`%s`: %s\n' % (values['name'], values['website']) |
| return result |
| |
| |
| def generate_supported_providers_table(api, provider_matrix): |
| data = [] |
| header = ['Provider', 'Documentation', 'Provider Constant', |
| 'Supported Regions', 'Module', 'Class Name'] |
| |
| data.append(header) |
| for provider, values in sorted(provider_matrix.items()): |
| name_str = '`%s`_' % (values['name']) |
| module_str = ':mod:`%s`' % (values['module']) |
| class_str = ':class:`%s`' % (values['class']) |
| |
| # Ignore old deprecated driver class per region S3 drivers |
| if 'Amazon S3 (' in values['name']: |
| continue |
| |
| params = {'api': api, 'provider': provider.lower()} |
| driver_docs_path = pjoin(this_dir, |
| '../docs/%(api)s/drivers/%(provider)s.rst' |
| % params) |
| |
| if os.path.exists(driver_docs_path): |
| docs_link = ':doc:`Click </%(api)s/drivers/%(provider)s>`' % params |
| else: |
| docs_link = '' |
| |
| cls = values['cls'] |
| supported_regions = cls.list_regions() if hasattr(cls, 'list_regions') \ |
| else None |
| |
| if supported_regions: |
| # Sort the regions to achieve stable output |
| supported_regions = sorted(supported_regions) |
| supported_regions = ', '.join(supported_regions) |
| else: |
| supported_regions = 'single region driver' |
| |
| row = [name_str, docs_link, values['constant'], supported_regions, |
| module_str, class_str] |
| data.append(row) |
| |
| result = generate_rst_table(data) |
| |
| result += '\n\n' |
| for provider, values in sorted(provider_matrix.items()): |
| result += '.. _`%s`: %s\n' % (values['name'], values['website']) |
| return result |
| |
| |
| def generate_tables(): |
| apis = BASE_API_METHODS.keys() |
| for api in apis: |
| result = generate_providers_table(api) |
| |
| docs_dir = api |
| |
| if api.startswith('compute'): |
| docs_dir = 'compute' |
| elif api.startswith('storage'): |
| docs_dir = 'storage' |
| |
| supported_providers = generate_supported_providers_table(docs_dir, |
| result) |
| supported_methods = generate_supported_methods_table(api, result) |
| |
| current_path = os.path.dirname(__file__) |
| target_dir = os.path.abspath(pjoin(current_path, |
| '../docs/%s/' % (docs_dir))) |
| |
| file_name_1 = '_supported_providers.rst' |
| file_name_2 = '_supported_methods.rst' |
| |
| if api == 'compute_main': |
| file_name_2 = '_supported_methods_main.rst' |
| elif api == 'compute_image_management': |
| file_name_2 = '_supported_methods_image_management.rst' |
| elif api == 'compute_block_storage': |
| file_name_2 = '_supported_methods_block_storage.rst' |
| elif api == 'compute_key_pair_management': |
| file_name_2 = '_supported_methods_key_pair_management.rst' |
| elif api == 'storage_main': |
| file_name_2 = '_supported_methods_main.rst' |
| elif api == 'storage_cdn': |
| file_name_2 = '_supported_methods_cdn.rst' |
| |
| supported_providers_path = pjoin(target_dir, file_name_1) |
| supported_methods_path = pjoin(target_dir, file_name_2) |
| |
| with open(supported_providers_path, 'w') as fp: |
| fp.write(HEADER + '\n\n') |
| fp.write(supported_providers) |
| |
| with open(supported_methods_path, 'w') as fp: |
| fp.write(HEADER + '\n\n') |
| fp.write(supported_methods) |
| |
| |
| generate_tables() |