diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index dad452d09..4a5b50d8a 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -5723,6 +5723,31 @@ - [ ] validate_resource_policy +## service-quotas +
+10% implemented + +- [ ] associate_service_quota_template +- [ ] delete_service_quota_increase_request_from_template +- [ ] disassociate_service_quota_template +- [ ] get_association_for_service_quota_template +- [ ] get_aws_default_service_quota +- [ ] get_requested_service_quota_change +- [X] get_service_quota +- [ ] get_service_quota_increase_request_from_template +- [X] list_aws_default_service_quotas +- [ ] list_requested_service_quota_change_history +- [ ] list_requested_service_quota_change_history_by_quota +- [ ] list_service_quota_increase_requests_in_template +- [ ] list_service_quotas +- [ ] list_services +- [ ] list_tags_for_resource +- [ ] put_service_quota_increase_request_into_template +- [ ] request_service_quota_increase +- [ ] tag_resource +- [ ] untag_resource +
+ ## servicediscovery
61% implemented @@ -6537,7 +6562,6 @@ - schemas - securityhub - serverlessrepo -- service-quotas - servicecatalog - servicecatalog-appregistry - sesv2 diff --git a/MANIFEST.in b/MANIFEST.in index 16002563f..c4c45ceb7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,6 +9,7 @@ include moto/ec2/resources/amis.json include moto/cognitoidp/resources/*.json include moto/dynamodb/parsing/reserved_keywords.txt include moto/moto_api/_internal/* +include moto/servicequotas/resources/*/*.json include moto/ssm/resources/*.json include moto/ssm/resources/ami-amazon-linux-latest/*.json include moto/support/resources/*.json diff --git a/docs/docs/services/service-quotas.rst b/docs/docs/services/service-quotas.rst new file mode 100644 index 000000000..bbbd58af3 --- /dev/null +++ b/docs/docs/services/service-quotas.rst @@ -0,0 +1,54 @@ +.. _implementedservice_service-quotas: + +.. |start-h3| raw:: html + +

+ +.. |end-h3| raw:: html + +

+ +============== +service-quotas +============== + +.. autoclass:: moto.servicequotas.models.ServiceQuotasBackend + +|start-h3| Example usage |end-h3| + +.. sourcecode:: python + + @mock_servicequotas + def test_servicequotas_behaviour: + boto3.client("service-quotas") + ... + + + +|start-h3| Implemented features for this service |end-h3| + +- [ ] associate_service_quota_template +- [ ] delete_service_quota_increase_request_from_template +- [ ] disassociate_service_quota_template +- [ ] get_association_for_service_quota_template +- [ ] get_aws_default_service_quota +- [ ] get_requested_service_quota_change +- [X] get_service_quota +- [ ] get_service_quota_increase_request_from_template +- [X] list_aws_default_service_quotas + + The ServiceCodes that are currently implemented are: vpc + Pagination is not yet implemented. + + +- [ ] list_requested_service_quota_change_history +- [ ] list_requested_service_quota_change_history_by_quota +- [ ] list_service_quota_increase_requests_in_template +- [ ] list_service_quotas +- [ ] list_services +- [ ] list_tags_for_resource +- [ ] put_service_quota_increase_request_into_template +- [ ] request_service_quota_increase +- [ ] tag_resource +- [ ] untag_resource + diff --git a/moto/__init__.py b/moto/__init__.py index efabfa827..660ef0ee2 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -138,6 +138,9 @@ mock_s3control = lazy_load(".s3control", "mock_s3control") mock_sagemaker = lazy_load(".sagemaker", "mock_sagemaker") mock_sdb = lazy_load(".sdb", "mock_sdb") mock_secretsmanager = lazy_load(".secretsmanager", "mock_secretsmanager") +mock_servicequotas = lazy_load( + ".servicequotas", "mock_servicequotas", boto3_name="service-quotas" +) mock_ses = lazy_load(".ses", "mock_ses") mock_servicediscovery = lazy_load(".servicediscovery", "mock_servicediscovery") mock_signer = lazy_load(".signer", "mock_signer", boto3_name="signer") diff --git a/moto/backend_index.py b/moto/backend_index.py index d634817fd..9bc4d4765 100644 --- a/moto/backend_index.py +++ b/moto/backend_index.py @@ -148,6 +148,7 @@ backend_url_patterns = [ "servicediscovery", re.compile("https?://servicediscovery\\.(.+)\\.amazonaws\\.com"), ), + ("service-quotas", re.compile("https?://servicequotas\\.(.+)\\.amazonaws\\.com")), ("ses", re.compile("https?://email\\.(.+)\\.amazonaws\\.com")), ("ses", re.compile("https?://ses\\.(.+)\\.amazonaws\\.com")), ("signer", re.compile("https?://signer\\.(.+)\\.amazonaws\\.com")), diff --git a/moto/servicequotas/__init__.py b/moto/servicequotas/__init__.py new file mode 100644 index 000000000..d8e8601ef --- /dev/null +++ b/moto/servicequotas/__init__.py @@ -0,0 +1,5 @@ +"""servicequotas module initialization; sets value for base decorator.""" +from .models import servicequotas_backends +from ..core.models import base_decorator + +mock_servicequotas = base_decorator(servicequotas_backends) diff --git a/moto/servicequotas/exceptions.py b/moto/servicequotas/exceptions.py new file mode 100644 index 000000000..b84cae248 --- /dev/null +++ b/moto/servicequotas/exceptions.py @@ -0,0 +1,10 @@ +"""Exceptions raised by the servicequotas service.""" +from moto.core.exceptions import JsonRESTError + + +class NoSuchResource(JsonRESTError): + def __init__(self) -> None: + super().__init__( + "NoSuchResourceException", + "This service is not available in the current Region. Choose a different Region or a different service.", + ) diff --git a/moto/servicequotas/models.py b/moto/servicequotas/models.py new file mode 100644 index 000000000..1860a4734 --- /dev/null +++ b/moto/servicequotas/models.py @@ -0,0 +1,35 @@ +"""ServiceQuotasBackend class with methods for supported APIs.""" + +from moto.core import BaseBackend +from moto.core.utils import BackendDict +from typing import Any, Dict, List +from .exceptions import NoSuchResource +from .resources.default_quotas.vpc import VPC_DEFAULT_QUOTAS + + +class ServiceQuotasBackend(BaseBackend): + """Implementation of ServiceQuotas APIs.""" + + def __init__(self, region_name: str, account_id: str): + super().__init__(region_name, account_id) + + def list_aws_default_service_quotas( + self, service_code: str + ) -> List[Dict[str, Any]]: + """ + The ServiceCodes that are currently implemented are: vpc + Pagination is not yet implemented. + """ + if service_code == "vpc": + return VPC_DEFAULT_QUOTAS + raise NoSuchResource + + def get_service_quota(self, service_code: str, quota_code: str) -> Dict[str, Any]: + if service_code == "vpc": + for quota in VPC_DEFAULT_QUOTAS: + if quota["QuotaCode"] == quota_code: + return quota + raise NoSuchResource + + +servicequotas_backends = BackendDict(ServiceQuotasBackend, "service-quotas") diff --git a/moto/servicequotas/resources/__init__.py b/moto/servicequotas/resources/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/moto/servicequotas/resources/default_quotas/__init__.py b/moto/servicequotas/resources/default_quotas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/moto/servicequotas/resources/default_quotas/vpc.py b/moto/servicequotas/resources/default_quotas/vpc.py new file mode 100644 index 000000000..896aeb879 --- /dev/null +++ b/moto/servicequotas/resources/default_quotas/vpc.py @@ -0,0 +1,277 @@ +VPC_DEFAULT_QUOTAS = [ + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-7E9ECCDB", + "QuotaCode": "L-7E9ECCDB", + "QuotaName": "Active VPC peering connections per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 50.0, + }, + { + "Adjustable": False, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-3248932A", + "QuotaCode": "L-3248932A", + "QuotaName": "Characters per VPC endpoint policy", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 20480.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-45FE3B85", + "QuotaCode": "L-45FE3B85", + "QuotaName": "Egress-only internet gateways per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-1B52E74A", + "QuotaCode": "L-1B52E74A", + "QuotaName": "Gateway VPC endpoints per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 20.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-83CA0A9D", + "QuotaCode": "L-83CA0A9D", + "QuotaName": "IPv4 CIDR blocks per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-085A6257", + "QuotaCode": "L-085A6257", + "QuotaName": "IPv6 CIDR blocks per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-0EA8095F", + "QuotaCode": "L-0EA8095F", + "QuotaName": "Inbound or outbound rules per security group", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 60.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-29B6F2EB", + "QuotaCode": "L-29B6F2EB", + "QuotaName": "Interface VPC endpoints per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 50.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-A4707A72", + "QuotaCode": "L-A4707A72", + "QuotaName": "Internet gateways per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-FE5A380F", + "QuotaCode": "L-FE5A380F", + "QuotaName": "NAT gateways per Availability Zone", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-B4A6D682", + "QuotaCode": "L-B4A6D682", + "QuotaName": "Network ACLs per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 200.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-BB24F6E5", + "QuotaCode": "L-BB24F6E5", + "QuotaName": "Network Address Usage", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 64000.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-DF5E4CA3", + "QuotaCode": "L-DF5E4CA3", + "QuotaName": "Network interfaces per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5000.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-DC9F7029", + "QuotaCode": "L-DC9F7029", + "QuotaName": "Outstanding VPC peering connection requests", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 25.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-2C462E13", + "QuotaCode": "L-2C462E13", + "QuotaName": "Participant accounts per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 100.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-CD17FD4B", + "QuotaCode": "L-CD17FD4B", + "QuotaName": "Peered Network Address Usage", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 128000.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-589F43AA", + "QuotaCode": "L-589F43AA", + "QuotaName": "Route tables per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 200.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-93826ACB", + "QuotaCode": "L-93826ACB", + "QuotaName": "Routes per route table", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 50.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-2AEEBF1A", + "QuotaCode": "L-2AEEBF1A", + "QuotaName": "Rules per network ACL", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 20.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-2AFB9258", + "QuotaCode": "L-2AFB9258", + "QuotaName": "Security groups per network interface", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-407747CB", + "QuotaCode": "L-407747CB", + "QuotaName": "Subnets per VPC", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 200.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-44499CD2", + "QuotaCode": "L-44499CD2", + "QuotaName": "Subnets that can be shared with an account", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 100.0, + }, + { + "Adjustable": False, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-8312C5BB", + "QuotaCode": "L-8312C5BB", + "QuotaName": "VPC peering connection request expiry hours", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 168.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-E79EC296", + "QuotaCode": "L-E79EC296", + "QuotaName": "VPC security groups per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 2500.0, + }, + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-F678F1CE", + "QuotaCode": "L-F678F1CE", + "QuotaName": "VPCs per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + }, +] diff --git a/moto/servicequotas/responses.py b/moto/servicequotas/responses.py new file mode 100644 index 000000000..40442e4e6 --- /dev/null +++ b/moto/servicequotas/responses.py @@ -0,0 +1,33 @@ +"""Handles incoming servicequotas requests, invokes methods, returns responses.""" +import json + +from moto.core.responses import BaseResponse +from .models import servicequotas_backends, ServiceQuotasBackend + + +class ServiceQuotasResponse(BaseResponse): + """Handler for ServiceQuotas requests and responses.""" + + def __init__(self) -> None: + super().__init__(service_name="service-quotas") + + @property + def backend(self) -> ServiceQuotasBackend: + """Return backend instance specific for this region.""" + return servicequotas_backends[self.current_account][self.region] + + def list_aws_default_service_quotas(self) -> str: + params = json.loads(self.body) + service_code = str(params.get("ServiceCode")) + quotas = self.backend.list_aws_default_service_quotas(service_code) + return json.dumps(dict(Quotas=quotas)) + + def get_service_quota(self) -> str: + params = json.loads(self.body) + service_code = str(params.get("ServiceCode")) + quota_code = str(params.get("QuotaCode")) + quota = self.backend.get_service_quota( + service_code=service_code, + quota_code=quota_code, + ) + return json.dumps(dict(Quota=quota)) diff --git a/moto/servicequotas/urls.py b/moto/servicequotas/urls.py new file mode 100644 index 000000000..a6a870cf6 --- /dev/null +++ b/moto/servicequotas/urls.py @@ -0,0 +1,11 @@ +"""servicequotas base URL and path.""" +from .responses import ServiceQuotasResponse + +url_bases = [ + r"https?://servicequotas\.(.+)\.amazonaws\.com", +] + + +url_paths = { + "{0}/$": ServiceQuotasResponse.dispatch, +} diff --git a/tests/terraformtests/terraform-tests.success.txt b/tests/terraformtests/terraform-tests.success.txt index c2eaa21aa..d66058a7d 100644 --- a/tests/terraformtests/terraform-tests.success.txt +++ b/tests/terraformtests/terraform-tests.success.txt @@ -87,10 +87,7 @@ ec2: - TestAccEC2CarrierGateway_ - TestAccEC2InstanceTypeOfferingDataSource_ - TestAccEC2InstanceTypeOfferingsDataSource_ - - TestAccEC2InternetGateway_ - - TestAccEC2NATGateway_ - TestAccEC2RouteTableAssociation_ - - TestAccEC2SecurityGroups - TestAccEC2SpotInstanceRequest_disappears - TestAccEC2SpotInstanceRequest_interruptUpdate - TestAccEC2VPCEndpointService_ @@ -99,6 +96,42 @@ ec2: - TestAccEC2VPNGateway_ - TestAccEC2VPNGatewayAttachment_ - TestAccVPC_ + - TestAccVPCEgressOnlyInternetGateway_ + - TestAccVPCInternetGateway + - TestAccVPCNATGateway_ + - TestAccVPCSecurityGroupDataSource_basic + - TestAccVPCSecurityGroupRule_ + - TestAccVPCSecurityGroup_allowAll + - TestAccVPCSecurityGroup_basic + - TestAccVPCSecurityGroup_change + - TestAccVPCSecurityGroup_cidrAndGroups + - TestAccVPCSecurityGroup_defaultEgressVPC + - TestAccVPCSecurityGroup_disappears + - TestAccVPCSecurityGroup_driftComplex + - TestAccVPCSecurityGroup_egressMode + - TestAccVPCSecurityGroup_egressWithPrefixList + - TestAccVPCSecurityGroup_failWithDiffMismatch + - TestAccVPCSecurityGroup_ingressMode + - TestAccVPCSecurityGroup_ingressWithCIDRAndSGsVPC + - TestAccVPCSecurityGroup_ingressWithPrefixList + - TestAccVPCSecurityGroup_invalidCIDRBlock + - TestAccVPCSecurityGroup_ipRangeAndSecurityGroupWithSameRules + - TestAccVPCSecurityGroup_ipRangesWithSameRules + - TestAccVPCSecurityGroup_ipv4AndIPv6Egress + - TestAccVPCSecurityGroup_ipv6 + - TestAccVPCSecurityGroup_multiIngress + - TestAccVPCSecurityGroup_nameGenerated + - TestAccVPCSecurityGroup_namePrefix + - TestAccVPCSecurityGroup_namePrefixTerraform + - TestAccVPCSecurityGroup_nameTerraformPrefix + - TestAccVPCSecurityGroup_noVPC + - TestAccVPCSecurityGroup_ruleDescription + - TestAccVPCSecurityGroup_ruleGathering + - TestAccVPCSecurityGroup_self + - TestAccVPCSecurityGroup_sourceSecurityGroup + - TestAccVPCSecurityGroup_tags + - TestAccVPCSecurityGroup_vpc + - TestAccVPCSecurityGroups ecr: - TestAccECRLifecyclePolicy - TestAccECRRegistryPolicy diff --git a/tests/test_servicequotas/__init__.py b/tests/test_servicequotas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_servicequotas/test_servicequotas.py b/tests/test_servicequotas/test_servicequotas.py new file mode 100644 index 000000000..0380af28a --- /dev/null +++ b/tests/test_servicequotas/test_servicequotas.py @@ -0,0 +1,77 @@ +"""Unit tests for servicequotas-supported APIs.""" +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import +from moto import mock_servicequotas +from botocore.exceptions import ClientError + +# See our Development Tips on writing tests for hints on how to write good tests: +# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html + + +@mock_servicequotas +def test_list_aws_default_service_quotas(): + client = boto3.client("service-quotas", region_name="eu-west-1") + resp = client.list_aws_default_service_quotas(ServiceCode="vpc") + + resp.should.have.key("Quotas").length_of(25) + + resp["Quotas"].should.contain( + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-2AFB9258", + "QuotaCode": "L-2AFB9258", + "QuotaName": "Security groups per network interface", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + } + ) + resp["Quotas"].should.contain( + { + "Adjustable": True, + "GlobalQuota": False, + "QuotaArn": "arn:aws:servicequotas:eu-west-1::vpc/L-F678F1CE", + "QuotaCode": "L-F678F1CE", + "QuotaName": "VPCs per Region", + "ServiceCode": "vpc", + "ServiceName": "Amazon Virtual Private Cloud (Amazon VPC)", + "Unit": "None", + "Value": 5.0, + } + ) + + +@mock_servicequotas +def test_list_defaults_for_unknown_service(): + client = boto3.client("service-quotas", "us-east-1") + + with pytest.raises(ClientError) as exc: + client.list_aws_default_service_quotas(ServiceCode="unknown") + err = exc.value.response["Error"] + err["Code"].should.equal("NoSuchResourceException") + err["Message"].should.equal( + "This service is not available in the current Region. Choose a different Region or a different service." + ) + + +@mock_servicequotas +def test_get_service_quota(): + client = boto3.client("service-quotas", region_name="us-east-2") + quotas = client.list_aws_default_service_quotas(ServiceCode="vpc")["Quotas"] + + for quota in quotas: + resp = client.get_service_quota(ServiceCode="vpc", QuotaCode=quota["QuotaCode"]) + quota.should.equal(resp["Quota"]) + + +@mock_servicequotas +def test_get_unknown_service_quota(): + client = boto3.client("service-quotas", region_name="us-east-2") + + with pytest.raises(ClientError) as exc: + client.get_service_quota(ServiceCode="vpc", QuotaCode="unknown") + err = exc.value.response["Error"] + err["Code"].should.equal("NoSuchResourceException")