# Copyright (c) 2013 VMware, Inc. All rights reserved. # # 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. # """Schema change history date: 2018-03-15 changes: - (incompatible) Removed the `hosts` table for OS hosts information because access to the information has been removed from the latest Nova API and client. - Added the `hypervisors` table for hypervisor information. date: 2017-10-01 changes: - Added the `tags` table for server tags information. """ from __future__ import print_function from __future__ import division from __future__ import absolute_import import novaclient.client from oslo_log import log as logging import six from congress.datasources import constants from congress.datasources import datasource_driver from congress.datasources import datasource_utils as ds_utils LOG = logging.getLogger(__name__) class NovaDriver(datasource_driver.PollingDataSourceDriver, datasource_driver.ExecutionDriver): SERVERS = "servers" FLAVORS = "flavors" HYPERVISORS = "hypervisors" SERVICES = 'services' AVAILABILITY_ZONES = "availability_zones" TAGS = "tags" # This is the most common per-value translator, so define it once here. value_trans = {'translation-type': 'VALUE'} def safe_id(x): if isinstance(x, six.string_types): return x try: return x['id'] except Exception: return str(x) servers_translator = { 'translation-type': 'HDICT', 'table-name': SERVERS, 'selector-type': 'DOT_SELECTOR', 'field-translators': ({'fieldname': 'id', 'desc': 'The UUID for the server', 'translator': value_trans}, {'fieldname': 'name', 'desc': 'Name of the server', 'translator': value_trans}, {'fieldname': 'hostId', 'col': 'host_id', 'desc': 'The UUID for the host', 'translator': value_trans}, {'fieldname': 'status', 'desc': 'The server status', 'translator': value_trans}, {'fieldname': 'tenant_id', 'desc': 'The tenant ID', 'translator': value_trans}, {'fieldname': 'user_id', 'desc': 'The user ID of the user who owns the server', 'translator': value_trans}, {'fieldname': 'image', 'col': 'image_id', 'desc': 'Name or ID of image', 'translator': {'translation-type': 'VALUE', 'extract-fn': safe_id}}, {'fieldname': 'flavor', 'col': 'flavor_id', 'desc': 'ID of the flavor', 'translator': {'translation-type': 'VALUE', 'extract-fn': safe_id}}, {'fieldname': 'OS-EXT-AZ:availability_zone', 'col': 'zone', 'desc': 'The availability zone of host', 'translator': value_trans}, {'fieldname': 'OS-EXT-SRV-ATTR:hypervisor_hostname', 'desc': ('The hostname of hypervisor where the server is ' 'running'), 'col': 'host_name', 'translator': value_trans}, {'fieldname': 'tags', 'translator': {'translation-type': 'LIST', 'table-name': TAGS, 'parent-key': 'id', 'parent-col-name': 'server_id', 'parent-key-desc': 'UUID of server', 'val-col': 'tag', 'val-col-desc': 'server tag string', 'translator': value_trans}})} flavors_translator = { 'translation-type': 'HDICT', 'table-name': FLAVORS, 'selector-type': 'DOT_SELECTOR', 'field-translators': ({'fieldname': 'id', 'desc': 'ID of the flavor', 'translator': value_trans}, {'fieldname': 'name', 'desc': 'Name of the flavor', 'translator': value_trans}, {'fieldname': 'vcpus', 'desc': 'Number of vcpus', 'translator': value_trans}, {'fieldname': 'ram', 'desc': 'Memory size in MB', 'translator': value_trans}, {'fieldname': 'disk', 'desc': 'Disk size in GB', 'translator': value_trans}, {'fieldname': 'ephemeral', 'desc': 'Ephemeral space size in GB', 'translator': value_trans}, {'fieldname': 'rxtx_factor', 'desc': 'RX/TX factor', 'translator': value_trans})} hypervisors_translator = { 'translation-type': 'HDICT', 'table-name': HYPERVISORS, 'selector-type': 'DOT_SELECTOR', 'field-translators': ({'fieldname': 'hypervisor_hostname', 'desc': 'Hypervisor host', 'translator': value_trans}, {'fieldname': 'id', 'desc': 'hypervisori id', 'translator': value_trans}, {'fieldname': 'state', 'desc': 'State of the hypervisor', 'translator': value_trans}, {'fieldname': 'status', 'desc': 'Status of the hypervisor', 'translator': value_trans})} services_translator = { 'translation-type': 'HDICT', 'table-name': SERVICES, 'selector-type': 'DOT_SELECTOR', 'field-translators': ({'fieldname': 'id', 'col': 'service_id', 'desc': 'Service ID', 'translator': value_trans}, {'fieldname': 'binary', 'desc': 'Service binary', 'translator': value_trans}, {'fieldname': 'host', 'desc': 'Host Name', 'translator': value_trans}, {'fieldname': 'zone', 'desc': 'Availability Zone', 'translator': value_trans}, {'fieldname': 'status', 'desc': 'Status of service', 'translator': value_trans}, {'fieldname': 'state', 'desc': 'State of service', 'translator': value_trans}, {'fieldname': 'updated_at', 'desc': 'Last updated time', 'translator': value_trans}, {'fieldname': 'disabled_reason', 'desc': 'Disabled reason', 'translator': value_trans})} availability_zones_translator = { 'translation-type': 'HDICT', 'table-name': AVAILABILITY_ZONES, 'selector-type': 'DOT_SELECTOR', 'field-translators': ({'fieldname': 'zoneName', 'col': 'zone', 'desc': 'Availability zone name', 'translator': value_trans}, {'fieldname': 'zoneState', 'col': 'state', 'desc': 'Availability zone state', 'translator': value_trans})} TRANSLATORS = [servers_translator, flavors_translator, services_translator, hypervisors_translator, availability_zones_translator] def __init__(self, name='', args=None): super(NovaDriver, self).__init__(name, args) datasource_driver.ExecutionDriver.__init__(self) self.creds = args session = ds_utils.get_keystone_session(self.creds) self.nova_client = novaclient.client.Client( version=self.creds.get('api_version', '2.26'), session=session) self.add_executable_method('servers_set_meta', [{'name': 'server', 'description': 'server id'}, {'name': 'meta-key1', 'description': 'meta key 1'}, {'name': 'meta-value1', 'description': 'value for meta key1'}, {'name': 'meta-keyN', 'description': 'meta key N'}, {'name': 'meta-valueN', 'description': 'value for meta keyN'}], "A wrapper for servers.set_meta()") self.add_executable_client_methods(self.nova_client, 'novaclient.v2.') self.initialize_update_methods() self._init_end_start_poll() @staticmethod def get_datasource_info(): result = {} result['id'] = 'nova' result['description'] = ('Datasource driver that interfaces with ' 'OpenStack Compute aka nova.') result['config'] = ds_utils.get_openstack_required_config() result['config']['api_version'] = constants.OPTIONAL result['config']['lazy_tables'] = constants.OPTIONAL result['secret'] = ['password'] return result def initialize_update_methods(self): servers_method = lambda: self._translate_servers( self.nova_client.servers.list( detailed=True, search_opts={"all_tenants": 1})) self.add_update_method(servers_method, self.servers_translator) flavors_method = lambda: self._translate_flavors( self.nova_client.flavors.list()) self.add_update_method(flavors_method, self.flavors_translator) hypervisors_method = lambda: self._translate_hypervisors( self.nova_client.hypervisors.list()) self.add_update_method(hypervisors_method, self.hypervisors_translator) services_method = lambda: self._translate_services( self.nova_client.services.list()) self.add_update_method(services_method, self.services_translator) az_method = lambda: self._translate_availability_zones( self.nova_client.availability_zones.list()) self.add_update_method(az_method, self.availability_zones_translator) @ds_utils.update_state_on_changed(SERVERS) def _translate_servers(self, obj): row_data = NovaDriver.convert_objs(obj, NovaDriver.servers_translator) return row_data @ds_utils.update_state_on_changed(FLAVORS) def _translate_flavors(self, obj): row_data = NovaDriver.convert_objs(obj, NovaDriver.flavors_translator) return row_data @ds_utils.update_state_on_changed(HYPERVISORS) def _translate_hypervisors(self, obj): row_data = NovaDriver.convert_objs( obj, NovaDriver.hypervisors_translator) return row_data @ds_utils.update_state_on_changed(SERVICES) def _translate_services(self, obj): row_data = NovaDriver.convert_objs(obj, NovaDriver.services_translator) return row_data @ds_utils.update_state_on_changed(AVAILABILITY_ZONES) def _translate_availability_zones(self, obj): row_data = NovaDriver.convert_objs( obj, NovaDriver.availability_zones_translator) return row_data def execute(self, action, action_args): """Overwrite ExecutionDriver.execute().""" # action can be written as a method or an API call. func = getattr(self, action, None) if func and self.is_executable(func): func(action_args) else: self._execute_api(self.nova_client, action, action_args) # "action" methods - to be used with "execute" def servers_set_meta(self, args): """A wrapper for servers.set_meta(). 'execute[p(x)]' doesn't take optional args at the moment. Therefore, this function translates the positional ARGS to optional args and call the servers.set_meta() api. :param: args: expected server ID and pairs of meta data in positional args such as:: {'positional': ['server_id', 'meta1', 'value1', 'meta2', 'value2']} Usage:: execute[nova.servers_set_meta(svr_id, meta1, val1, meta2, val2) :- triggering_table(id) """ action = 'servers.set_meta' positional_args = args.get('positional', []) if not positional_args: LOG.error('Args not found for servers_set_meta()') return # Strip off the server_id before converting meta data pairs server_id = positional_args.pop(0) meta_data = self._convert_args(positional_args) action_args = {'named': {'server': server_id, 'metadata': meta_data}} self._execute_api(self.nova_client, action, action_args)