Implement DescribeAccountAttributes action

Change-Id: I2e67e88f5e804fc400109c762ca9b9be1a8f930e
This commit is contained in:
Feodor Tersin 2015-02-11 00:50:23 +03:00
parent c754004c21
commit 7377e3f7d9
5 changed files with 157 additions and 11 deletions

View File

@ -16,6 +16,7 @@ from oslo.config import cfg
from ec2api.api import clients from ec2api.api import clients
from ec2api.api import common from ec2api.api import common
from ec2api import exception
from ec2api.openstack.common import log as logging from ec2api.openstack.common import log as logging
from ec2api import utils from ec2api import utils
@ -49,13 +50,23 @@ CONF = cfg.CONF
CONF.register_opts(availability_zone_opts) CONF.register_opts(availability_zone_opts)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
"""Availability zones and regions related API implementation """Availability zones, regions, account attributes related API implementation
""" """
# TODO(ft): implement messages, regionName AvailabilityZone properties
# TODO(ft): implement vpc-max-security-groups-per-interface, max-elastic-ips,
# vpc-max-elastic-ips Account Attributes
Validator = common.Validator Validator = common.Validator
def get_account_attribute_engine():
if CONF.full_vpc_support:
return AccountAttributeEngineNeutron()
else:
return AccountAttributeEngineNova()
class AvailabilityZoneDescriber(common.UniversalDescriber): class AvailabilityZoneDescriber(common.UniversalDescriber):
KIND = 'az' KIND = 'az'
@ -120,6 +131,29 @@ def describe_regions(context, region_name=None, filter=None):
return {'regionInfo': regions} return {'regionInfo': regions}
def describe_account_attributes(context, attribute_name=None):
def get_max_instances():
nova = clients.nova(context)
quotas = nova.quotas.get(context.project_id, context.user_id)
return quotas.instances
attribute_getters = {
'supported-platforms': (
account_attribute_engine.get_supported_platforms),
'default-vpc': account_attribute_engine.get_default_vpc,
'max-instances': get_max_instances,
}
formatted_attributes = []
for attribute in (attribute_name or attribute_getters):
if attribute not in attribute_getters:
raise exception.InvalidParameter(name=attribute)
formatted_attributes.append(
_format_account_attribute(attribute,
attribute_getters[attribute]()))
return {'accountAttributeSet': formatted_attributes}
def _format_availability_zone(zone): def _format_availability_zone(zone):
return {'zoneName': zone.zoneName, return {'zoneName': zone.zoneName,
'zoneState': ('available' 'zoneState': ('available'
@ -128,6 +162,13 @@ def _format_availability_zone(zone):
} }
def _format_account_attribute(attribute, value):
if not isinstance(value, list):
value = [value]
return {'attributeName': attribute,
'attributeValueSet': [{'attributeValue': val} for val in value]}
# NOTE(Alex): Openstack extension, AWS-incompability # NOTE(Alex): Openstack extension, AWS-incompability
# The whole function and its result is incompatible with AWS. # The whole function and its result is incompatible with AWS.
@ -152,3 +193,24 @@ def _describe_verbose(context):
values['updated_at']))}) values['updated_at']))})
return {'availabilityZoneInfo': formatted_availability_zones} return {'availabilityZoneInfo': formatted_availability_zones}
class AccountAttributeEngineNeutron(object):
def get_supported_platforms(self):
return ['EC2', 'VPC']
def get_default_vpc(self):
return 'none'
class AccountAttributeEngineNova(object):
def get_supported_platforms(self):
return ['EC2']
def get_default_vpc(self):
return 'none'
account_attribute_engine = get_account_attribute_engine()

View File

@ -320,7 +320,7 @@ class CloudController(object):
@module_and_param_types(security_group, 'sg_id', @module_and_param_types(security_group, 'sg_id',
'security_group_str', 'dummy') 'security_group_str', 'dummy')
def revoke_security_group_ingress(self, context, group_id=None, def revoke_security_group_ingress(self, context, group_id=None,
group_name=None, ip_permissions=None): group_name=None, ip_permissions=None):
"""Removes one or more ingress rules from a security group. """Removes one or more ingress rules from a security group.
Args: Args:
@ -357,7 +357,7 @@ class CloudController(object):
@module_and_param_types(security_group, 'sg_id', 'dummy') @module_and_param_types(security_group, 'sg_id', 'dummy')
def revoke_security_group_egress(self, context, group_id, def revoke_security_group_egress(self, context, group_id,
ip_permissions=None): ip_permissions=None):
"""Removes one or more egress rules from a security group for EC2-VPC. """Removes one or more egress rules from a security group for EC2-VPC.
Args: Args:
@ -676,6 +676,23 @@ class CloudController(object):
Specified regions. Specified regions.
""" """
@module_and_param_types(availability_zone, 'strs')
def describe_account_attributes(self, context, attribute_name=None):
"""Describes attributes of your EC2 account.
Args:
context (RequestContext): The request context.
attribute_name (list of str): One or more account attribute names.
The following are the supported account attributes:
supported-platforms | default-vpc | max-instances |
vpc-max-security-groups-per-interface (unsupported now) |
max-elastic-ips (unsupported now) |
vpc-max-elastic-ips (unsupported now)
Returns:
Information about one or more account attributes.
"""
@module_and_param_types(instance, 'i_id') @module_and_param_types(instance, 'i_id')
def get_password_data(self, context, instance_id): def get_password_data(self, context, instance_id):
"""Retrieves the encrypted administrator password for Windows instance. """Retrieves the encrypted administrator password for Windows instance.
@ -1606,8 +1623,8 @@ class VpcCloudController(CloudController):
Returns: Returns:
A list of network interfaces. A list of network interfaces.
""" """
return network_interface.describe_network_interfaces(context, return network_interface.describe_network_interfaces(
network_interface_id, filter) context, network_interface_id, filter)
@module_and_param_types(network_interface, 'eni_id', @module_and_param_types(network_interface, 'eni_id',
'str') 'str')
@ -1635,10 +1652,10 @@ class VpcCloudController(CloudController):
'bool', 'bool',
'sg_ids') 'sg_ids')
def modify_network_interface_attribute(self, context, def modify_network_interface_attribute(self, context,
network_interface_id, network_interface_id,
description=None, description=None,
source_dest_check=None, source_dest_check=None,
security_group_id=None): security_group_id=None):
"""Modifies the specified attribute of the specified network interface. """Modifies the specified attribute of the specified network interface.
@ -1661,8 +1678,8 @@ class VpcCloudController(CloudController):
@module_and_param_types(network_interface, 'eni_id', @module_and_param_types(network_interface, 'eni_id',
'str') 'str')
def reset_network_interface_attribute(self, context, def reset_network_interface_attribute(self, context,
network_interface_id, network_interface_id,
attribute): attribute):
"""Resets the specified attribute of the specified network interface. """Resets the specified attribute of the specified network interface.

View File

@ -288,6 +288,10 @@ class MissingParameter(Invalid):
msg_fmt = _("The required parameter '%(param)s' is missing") msg_fmt = _("The required parameter '%(param)s' is missing")
class InvalidParameter(Invalid):
msg_fmt = _("The property '%(name)s' is not valid")
class InvalidParameterValue(Invalid): class InvalidParameterValue(Invalid):
msg_fmt = _("Value (%(value)s) for parameter %(parameter)s is invalid. " msg_fmt = _("Value (%(value)s) for parameter %(parameter)s is invalid. "
"%(reason)s") "%(reason)s")

View File

@ -53,6 +53,7 @@ class ApiTestCase(test_base.BaseTestCase):
self.nova_security_group_rules = ( self.nova_security_group_rules = (
nova_mock.return_value.security_group_rules) nova_mock.return_value.security_group_rules)
self.nova_volumes = nova_mock.return_value.volumes self.nova_volumes = nova_mock.return_value.volumes
self.nova_quotas = nova_mock.return_value.quotas
self.addCleanup(nova_patcher.stop) self.addCleanup(nova_patcher.stop)
glance_patcher = mock.patch('glanceclient.client.Client') glance_patcher = mock.patch('glanceclient.client.Client')

View File

@ -12,6 +12,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import mock
from ec2api.api import availability_zone
from ec2api.tests.unit import base from ec2api.tests.unit import base
from ec2api.tests.unit import fakes from ec2api.tests.unit import fakes
from ec2api.tests.unit import matchers from ec2api.tests.unit import matchers
@ -50,3 +53,62 @@ class AvailabilityZoneCase(base.ApiTestCase):
self.assertEqual(resp['regionInfo'][0]['regionName'], 'nova') self.assertEqual(resp['regionInfo'][0]['regionName'], 'nova')
self.assertTrue(resp['regionInfo'][0].get('regionEndpoint') self.assertTrue(resp['regionInfo'][0].get('regionEndpoint')
is not None) is not None)
def test_describe_account_attributes(self):
self.nova_quotas.get.return_value = mock.Mock(instances=77)
availability_zone.account_attribute_engine = (
availability_zone.AccountAttributeEngineNeutron())
resp = self.execute('DescribeAccountAttributes', {})
self.assertEqual(200, resp['http_status_code'])
self.assertThat(resp['accountAttributeSet'],
matchers.ListMatches(
[{'attributeName': 'supported-platforms',
'attributeValueSet': [
{'attributeValue': 'EC2'},
{'attributeValue': 'VPC'}]},
{'attributeName': 'default-vpc',
'attributeValueSet': [
{'attributeValue': 'none'}]},
{'attributeName': 'max-instances',
'attributeValueSet': [
{'attributeValue': 77}]}],
orderless_lists=True))
self.nova_quotas.get.assert_called_once_with(
fakes.ID_OS_PROJECT, fakes.ID_OS_USER)
availability_zone.account_attribute_engine = (
availability_zone.AccountAttributeEngineNova())
resp = self.execute('DescribeAccountAttributes', {})
self.assertEqual(200, resp['http_status_code'])
self.assertThat(resp['accountAttributeSet'],
matchers.ListMatches(
[{'attributeName': 'supported-platforms',
'attributeValueSet': [
{'attributeValue': 'EC2'}]},
{'attributeName': 'default-vpc',
'attributeValueSet': [
{'attributeValue': 'none'}]},
{'attributeName': 'max-instances',
'attributeValueSet': [
{'attributeValue': 77}]}],
orderless_lists=True))
resp = self.execute('DescribeAccountAttributes',
{'AttributeName.1': 'default-vpc',
'AttributeName.2': 'max-instances'})
self.assertEqual(200, resp['http_status_code'])
self.assertThat(resp['accountAttributeSet'],
matchers.ListMatches(
[{'attributeName': 'default-vpc',
'attributeValueSet': [
{'attributeValue': 'none'}]},
{'attributeName': 'max-instances',
'attributeValueSet': [
{'attributeValue': 77}]}],
orderless_lists=True))
resp = self.execute('DescribeAccountAttributes',
{'AttributeName.1': 'fake'})
self.assertEqual(400, resp['http_status_code'])
self.assertEqual('InvalidParameter', resp['Error']['Code'])