diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 30c825839..2baf7eb53 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3587,6 +3587,34 @@ - [X] put_object +## mq +
+86% implemented + +- [X] create_broker +- [X] create_configuration +- [X] create_tags +- [X] create_user +- [X] delete_broker +- [X] delete_tags +- [X] delete_user +- [X] describe_broker +- [ ] describe_broker_engine_types +- [ ] describe_broker_instance_options +- [X] describe_configuration +- [X] describe_configuration_revision +- [X] describe_user +- [X] list_brokers +- [ ] list_configuration_revisions +- [X] list_configurations +- [X] list_tags +- [X] list_users +- [X] reboot_broker +- [X] update_broker +- [X] update_configuration +- [X] update_user +
+ ## opsworks
12% implemented @@ -5372,7 +5400,6 @@ - migrationhub-config - migrationhubstrategy - mobile -- mq - mturk - mwaa - neptune diff --git a/docs/docs/services/mq.rst b/docs/docs/services/mq.rst new file mode 100644 index 000000000..d863fd103 --- /dev/null +++ b/docs/docs/services/mq.rst @@ -0,0 +1,64 @@ +.. _implementedservice_mq: + +.. |start-h3| raw:: html + +

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

+ +== +mq +== + +.. autoclass:: moto.mq.models.MQBackend + +|start-h3| Example usage |end-h3| + +.. sourcecode:: python + + @mock_mq + def test_mq_behaviour: + boto3.client("mq") + ... + + + +|start-h3| Implemented features for this service |end-h3| + +- [X] create_broker +- [X] create_configuration +- [X] create_tags +- [X] create_user +- [X] delete_broker +- [X] delete_tags +- [X] delete_user +- [X] describe_broker +- [ ] describe_broker_engine_types +- [ ] describe_broker_instance_options +- [X] describe_configuration +- [X] describe_configuration_revision +- [X] describe_user +- [X] list_brokers + + Pagination is not yet implemented + + +- [ ] list_configuration_revisions +- [X] list_configurations + + Pagination has not yet been implemented. + + +- [X] list_tags +- [X] list_users +- [X] reboot_broker +- [X] update_broker +- [X] update_configuration + + No validation occurs on the provided XML. The authenticationStrategy may be changed depending on the provided configuration. + + +- [X] update_user + diff --git a/moto/__init__.py b/moto/__init__.py index 6458eafec..a61a6318c 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -84,6 +84,7 @@ mock_kinesis = lazy_load(".kinesis", "mock_kinesis") mock_kms = lazy_load(".kms", "mock_kms") mock_logs = lazy_load(".logs", "mock_logs") mock_managedblockchain = lazy_load(".managedblockchain", "mock_managedblockchain") +mock_mq = lazy_load(".mq", "mock_mq", boto3_name="mq") mock_opsworks = lazy_load(".opsworks", "mock_opsworks") mock_organizations = lazy_load(".organizations", "mock_organizations") mock_polly = lazy_load(".polly", "mock_polly") diff --git a/moto/backend_index.py b/moto/backend_index.py index 66a13001d..b14525e38 100644 --- a/moto/backend_index.py +++ b/moto/backend_index.py @@ -91,6 +91,7 @@ backend_url_patterns = [ ("mediapackage", re.compile("https?://mediapackage\\.(.+)\\.amazonaws.com")), ("mediastore", re.compile("https?://mediastore\\.(.+)\\.amazonaws\\.com")), ("mediastore-data", re.compile("https?://data.mediastore\\.(.+)\\.amazonaws.com")), + ("mq", re.compile("https?://mq\\.(.+)\\.amazonaws\\.com")), ("opsworks", re.compile("https?://opsworks\\.us-east-1\\.amazonaws.com")), ("organizations", re.compile("https?://organizations\\.(.+)\\.amazonaws\\.com")), ("polly", re.compile("https?://polly\\.(.+)\\.amazonaws.com")), diff --git a/moto/mq/__init__.py b/moto/mq/__init__.py new file mode 100644 index 000000000..cde94761c --- /dev/null +++ b/moto/mq/__init__.py @@ -0,0 +1,5 @@ +"""mq module initialization; sets value for base decorator.""" +from .models import mq_backends +from ..core.models import base_decorator + +mock_mq = base_decorator(mq_backends) diff --git a/moto/mq/configuration.py b/moto/mq/configuration.py new file mode 100644 index 000000000..6797ed2ee --- /dev/null +++ b/moto/mq/configuration.py @@ -0,0 +1,183 @@ +DEFAULT_CONFIGURATION_DATA = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" diff --git a/moto/mq/exceptions.py b/moto/mq/exceptions.py new file mode 100644 index 000000000..63a97fbe7 --- /dev/null +++ b/moto/mq/exceptions.py @@ -0,0 +1,71 @@ +import json +from moto.core.exceptions import JsonRESTError + + +class MQError(JsonRESTError): + pass + + +class UnknownBroker(MQError): + def __init__(self, broker_id): + super().__init__("NotFoundException", "Can't find requested broker") + self.broker_id = broker_id + + def get_body(self): + body = { + "errorAttribute": "broker-id", + "message": f"Can't find requested broker [{self.broker_id}]. Make sure your broker exists.", + } + return json.dumps(body) + + +class UnknownConfiguration(MQError): + def __init__(self, config_id): + super().__init__("NotFoundException", "Can't find requested configuration") + self.config_id = config_id + + def get_body(self): + body = { + "errorAttribute": "configuration_id", + "message": f"Can't find requested configuration [{self.config_id}]. Make sure your configuration exists.", + } + return json.dumps(body) + + +class UnknownUser(MQError): + def __init__(self, username): + super().__init__("NotFoundException", "Can't find requested user") + self.username = username + + def get_body(self): + body = { + "errorAttribute": "username", + "message": f"Can't find requested user [{self.username}]. Make sure your user exists.", + } + return json.dumps(body) + + +class UnsupportedEngineType(MQError): + def __init__(self, engine_type): + super().__init__("BadRequestException", "") + self.engine_type = engine_type + + def get_body(self): + body = { + "errorAttribute": "engineType", + "message": f"Broker engine type [{self.engine_type}] does not support configuration.", + } + return json.dumps(body) + + +class UnknownEngineType(MQError): + def __init__(self, engine_type): + super().__init__("BadRequestException", "") + self.engine_type = engine_type + + def get_body(self): + body = { + "errorAttribute": "engineType", + "message": f"Broker engine type [{self.engine_type}] is invalid. Valid values are: [ACTIVEMQ]", + } + return json.dumps(body) diff --git a/moto/mq/models.py b/moto/mq/models.py new file mode 100644 index 000000000..530b90bfc --- /dev/null +++ b/moto/mq/models.py @@ -0,0 +1,528 @@ +import base64 +import xmltodict + +from moto.core import ACCOUNT_ID, BaseBackend, BaseModel +from moto.core.utils import BackendDict, get_random_hex, unix_time +from moto.utilities.tagging_service import TaggingService + +from .configuration import DEFAULT_CONFIGURATION_DATA +from .exceptions import ( + UnknownBroker, + UnknownConfiguration, + UnknownUser, + UnsupportedEngineType, + UnknownEngineType, +) + + +class ConfigurationRevision(BaseModel): + def __init__(self, configuration_id, revision_id, description, data=None): + self.configuration_id = configuration_id + self.created = unix_time() + self.description = description + self.is_invalid = False + self.revision_id = revision_id + + if data is None: + self.data = base64.b64encode( + DEFAULT_CONFIGURATION_DATA.encode("UTF-8") + ).decode("utf-8") + else: + self.data = data + + def has_ldap_auth(self): + try: + xml = base64.b64decode(self.data) + dct = xmltodict.parse(xml, dict_constructor=dict) + return ( + "cachedLDAPAuthorizationMap" + in dct["broker"]["plugins"]["authorizationPlugin"]["map"] + ) + except Exception: + # There are many configurations to enable LDAP + # We're only checking for one here + # If anything fails, lets assume it's not LDAP + return False + + def to_json(self, full=True): + resp = { + "created": self.created, + "description": self.description, + "revision": int(self.revision_id), + } + if full: + resp["configurationId"] = self.configuration_id + resp["data"] = self.data + return resp + + +class Configuration(BaseModel): + def __init__(self, region, name, engine_type, engine_version): + self.id = f"c-{get_random_hex(6)}" + self.arn = f"arn:aws:mq:{region}:{ACCOUNT_ID}:configuration:{self.id}" + self.created = unix_time() + + self.name = name + self.engine_type = engine_type + self.engine_version = engine_version + + self.revisions = dict() + default_desc = ( + f"Auto-generated default for {self.name} on {engine_type} {engine_version}" + ) + latest_revision = ConfigurationRevision( + configuration_id=self.id, revision_id="1", description=default_desc + ) + self.revisions[latest_revision.revision_id] = latest_revision + + self.authentication_strategy = ( + "ldap" if latest_revision.has_ldap_auth() else "simple" + ) + + def update(self, data, description): + max_revision_id, _ = sorted(self.revisions.items())[-1] + next_revision_id = str(int(max_revision_id) + 1) + latest_revision = ConfigurationRevision( + configuration_id=self.id, + revision_id=next_revision_id, + description=description, + data=data, + ) + self.revisions[next_revision_id] = latest_revision + + self.authentication_strategy = ( + "ldap" if latest_revision.has_ldap_auth() else "simple" + ) + + def get_revision(self, revision_id): + return self.revisions[revision_id] + + def to_json(self): + _, latest_revision = sorted(self.revisions.items())[-1] + return { + "arn": self.arn, + "authenticationStrategy": self.authentication_strategy, + "created": self.created, + "engineType": self.engine_type, + "engineVersion": self.engine_version, + "id": self.id, + "name": self.name, + "latestRevision": latest_revision.to_json(full=False), + } + + +class User(BaseModel): + def __init__(self, broker_id, username, console_access=None, groups=None): + self.broker_id = broker_id + self.username = username + self.console_access = console_access or False + self.groups = groups or [] + + def update(self, console_access, groups): + if console_access is not None: + self.console_access = console_access + if groups: + self.groups = groups + + def summary(self): + return {"username": self.username} + + def to_json(self): + return { + "brokerId": self.broker_id, + "username": self.username, + "consoleAccess": self.console_access, + "groups": self.groups, + } + + +class Broker(BaseModel): + def __init__( + self, + name, + region, + authentication_strategy, + auto_minor_version_upgrade, + configuration, + deployment_mode, + encryption_options, + engine_type, + engine_version, + host_instance_type, + ldap_server_metadata, + logs, + maintenance_window_start_time, + publicly_accessible, + security_groups, + storage_type, + subnet_ids, + users, + ): + self.name = name + self.id = get_random_hex(6) + self.arn = f"arn:aws:mq:{region}:{ACCOUNT_ID}:broker:{self.id}" + self.state = "RUNNING" + self.created = unix_time() + + self.authentication_strategy = authentication_strategy + self.auto_minor_version_upgrade = auto_minor_version_upgrade + self.deployment_mode = deployment_mode + self.encryption_options = encryption_options + if not self.encryption_options: + self.encryption_options = {"useAwsOwnedKey": True} + self.engine_type = engine_type + self.engine_version = engine_version + self.host_instance_type = host_instance_type + self.ldap_server_metadata = ldap_server_metadata + self.logs = logs + if "general" not in self.logs: + self.logs["general"] = False + if "audit" not in self.logs: + if self.engine_type.upper() == "ACTIVEMQ": + self.logs["audit"] = False + self.maintenance_window_start_time = maintenance_window_start_time + if not self.maintenance_window_start_time: + self.maintenance_window_start_time = { + "dayOfWeek": "Sunday", + "timeOfDay": "00:00", + "timeZone": "UTC", + } + self.publicly_accessible = publicly_accessible + self.security_groups = security_groups + self.storage_type = storage_type + self.subnet_ids = subnet_ids + if not self.subnet_ids: + if self.deployment_mode == "CLUSTER_MULTI_AZ": + self.subnet_ids = [ + "default-az1", + "default-az2", + "default-az3", + "default-az4", + ] + elif self.deployment_mode == "ACTIVE_STANDBY_MULTI_AZ": + self.subnet_ids = ["active-subnet", "standby-subnet"] + else: + self.subnet_ids = ["default-subnet"] + + self.users = dict() + for user in users: + self.create_user( + username=user["username"], + groups=user.get("groups", []), + console_access=user.get("consoleAccess", False), + ) + + if self.engine_type.upper() == "RABBITMQ": + self.configurations = None + else: + current_config = configuration or { + "id": f"c-{get_random_hex(6)}", + "revision": 1, + } + self.configurations = { + "current": current_config, + "history": [], + } + if self.engine_type.upper() == "RABBITMQ": + console_url = f"https://0000.mq.{region}.amazonaws.com" + endpoints = ["amqps://mockmq:5671"] + else: + console_url = f"https://0000.mq.{region}.amazonaws.com:8162" + endpoints = [ + "ssl://mockmq:61617", + "amqp+ssl://mockmq:5671", + "stomp+ssl://mockmq:61614", + "mqtt+ssl://mockmq:8883", + "wss://mockmq:61619", + ] + self.instances = [ + { + "consoleURL": console_url, + "endpoints": endpoints, + "ipAddress": "192.168.0.1", + } + ] + + if deployment_mode == "ACTIVE_STANDBY_MULTI_AZ": + self.instances.append( + { + "consoleURL": console_url, + "endpoints": endpoints, + "ipAddress": "192.168.0.2", + } + ) + + def update( + self, + authentication_strategy, + auto_minor_version_upgrade, + configuration, + engine_version, + host_instance_type, + ldap_server_metadata, + logs, + maintenance_window_start_time, + security_groups, + ): + if authentication_strategy: + self.authentication_strategy = authentication_strategy + if auto_minor_version_upgrade is not None: + self.auto_minor_version_upgrade = auto_minor_version_upgrade + if configuration: + self.configurations["history"].append(self.configurations["current"]) + self.configurations["current"] = configuration + if engine_version: + self.engine_version = engine_version + if host_instance_type: + self.host_instance_type = host_instance_type + if ldap_server_metadata: + self.ldap_server_metadata = ldap_server_metadata + if logs: + self.logs = logs + if maintenance_window_start_time: + self.maintenance_window_start_time = maintenance_window_start_time + if security_groups: + self.security_groups = security_groups + + def reboot(self): + pass + + def create_user(self, username, console_access, groups): + user = User(self.id, username, console_access, groups) + self.users[username] = user + + def update_user(self, username, console_access, groups): + user = self.get_user(username) + user.update(console_access, groups) + + def get_user(self, username): + if username not in self.users: + raise UnknownUser(username) + return self.users[username] + + def delete_user(self, username): + self.users.pop(username, None) + + def list_users(self): + return self.users.values() + + def summary(self): + return { + "brokerArn": self.arn, + "brokerId": self.id, + "brokerName": self.name, + "brokerState": self.state, + "created": self.created, + "deploymentMode": self.deployment_mode, + "engineType": self.engine_type, + "hostInstanceType": self.host_instance_type, + } + + def to_json(self): + return { + "brokerId": self.id, + "brokerArn": self.arn, + "brokerName": self.name, + "brokerState": self.state, + "brokerInstances": self.instances, + "created": self.created, + "configurations": self.configurations, + "authenticationStrategy": self.authentication_strategy, + "autoMinorVersionUpgrade": self.auto_minor_version_upgrade, + "deploymentMode": self.deployment_mode, + "encryptionOptions": self.encryption_options, + "engineType": self.engine_type, + "engineVersion": self.engine_version, + "hostInstanceType": self.host_instance_type, + "ldapServerMetadata": self.ldap_server_metadata, + "logs": self.logs, + "maintenanceWindowStartTime": self.maintenance_window_start_time, + "publiclyAccessible": self.publicly_accessible, + "securityGroups": self.security_groups, + "storageType": self.storage_type, + "subnetIds": self.subnet_ids, + "users": [u.summary() for u in self.users.values()], + } + + +class MQBackend(BaseBackend): + """ + No EC2 integration exists yet - subnet ID's and security group values are not validated. Default values may not exist. + """ + + def __init__(self, region_name=None): + self.region_name = region_name + self.brokers = dict() + self.configs = dict() + self.tagger = TaggingService() + + def reset(self): + """Re-initialize all attributes for this instance.""" + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def create_broker( + self, + authentication_strategy, + auto_minor_version_upgrade, + broker_name, + configuration, + creator_request_id, + deployment_mode, + encryption_options, + engine_type, + engine_version, + host_instance_type, + ldap_server_metadata, + logs, + maintenance_window_start_time, + publicly_accessible, + security_groups, + storage_type, + subnet_ids, + tags, + users, + ): + broker = Broker( + name=broker_name, + region=self.region_name, + authentication_strategy=authentication_strategy, + auto_minor_version_upgrade=auto_minor_version_upgrade, + configuration=configuration, + deployment_mode=deployment_mode, + encryption_options=encryption_options, + engine_type=engine_type, + engine_version=engine_version, + host_instance_type=host_instance_type, + ldap_server_metadata=ldap_server_metadata, + logs=logs, + maintenance_window_start_time=maintenance_window_start_time, + publicly_accessible=publicly_accessible, + security_groups=security_groups, + storage_type=storage_type, + subnet_ids=subnet_ids, + users=users, + ) + self.brokers[broker.id] = broker + self.create_tags(broker.arn, tags) + return broker.arn, broker.id + + def delete_broker(self, broker_id): + del self.brokers[broker_id] + + def describe_broker(self, broker_id): + if broker_id not in self.brokers: + raise UnknownBroker(broker_id) + return self.brokers[broker_id] + + def reboot_broker(self, broker_id): + self.brokers[broker_id].reboot() + + def list_brokers(self): + """ + Pagination is not yet implemented + """ + return self.brokers.values() + + def create_user(self, broker_id, username, console_access, groups): + broker = self.describe_broker(broker_id) + broker.create_user(username, console_access, groups) + + def update_user(self, broker_id, console_access, groups, username): + broker = self.describe_broker(broker_id) + broker.update_user(username, console_access, groups) + + def describe_user(self, broker_id, username): + broker = self.describe_broker(broker_id) + return broker.get_user(username) + + def delete_user(self, broker_id, username): + broker = self.describe_broker(broker_id) + broker.delete_user(username) + + def list_users(self, broker_id): + broker = self.describe_broker(broker_id) + return broker.list_users() + + def create_configuration(self, name, engine_type, engine_version, tags): + if engine_type.upper() == "RABBITMQ": + raise UnsupportedEngineType(engine_type) + if engine_type.upper() != "ACTIVEMQ": + raise UnknownEngineType(engine_type) + config = Configuration( + region=self.region_name, + name=name, + engine_type=engine_type, + engine_version=engine_version, + ) + self.configs[config.id] = config + self.tagger.tag_resource( + config.arn, self.tagger.convert_dict_to_tags_input(tags) + ) + return config + + def update_configuration(self, config_id, data, description): + """ + No validation occurs on the provided XML. The authenticationStrategy may be changed depending on the provided configuration. + """ + config = self.configs[config_id] + config.update(data, description) + return config + + def describe_configuration(self, config_id): + if config_id not in self.configs: + raise UnknownConfiguration(config_id) + return self.configs[config_id] + + def describe_configuration_revision(self, config_id, revision_id): + config = self.configs[config_id] + return config.get_revision(revision_id) + + def list_configurations(self): + """ + Pagination has not yet been implemented. + """ + return self.configs.values() + + def create_tags(self, resource_arn, tags): + self.tagger.tag_resource( + resource_arn, self.tagger.convert_dict_to_tags_input(tags) + ) + + def list_tags(self, arn): + return self.tagger.get_tag_dict_for_resource(arn) + + def delete_tags(self, resource_arn, tag_keys): + if not isinstance(tag_keys, list): + tag_keys = [tag_keys] + self.tagger.untag_resource_using_names(resource_arn, tag_keys) + + def update_broker( + self, + authentication_strategy, + auto_minor_version_upgrade, + broker_id, + configuration, + engine_version, + host_instance_type, + ldap_server_metadata, + logs, + maintenance_window_start_time, + security_groups, + ): + broker = self.describe_broker(broker_id) + broker.update( + authentication_strategy=authentication_strategy, + auto_minor_version_upgrade=auto_minor_version_upgrade, + configuration=configuration, + engine_version=engine_version, + host_instance_type=host_instance_type, + ldap_server_metadata=ldap_server_metadata, + logs=logs, + maintenance_window_start_time=maintenance_window_start_time, + security_groups=security_groups, + ) + + +mq_backends = BackendDict(MQBackend, "mq") diff --git a/moto/mq/responses.py b/moto/mq/responses.py new file mode 100644 index 000000000..06276f55f --- /dev/null +++ b/moto/mq/responses.py @@ -0,0 +1,281 @@ +"""Handles incoming mq requests, invokes methods, returns responses.""" +import json +from functools import wraps +from urllib.parse import unquote + +from moto.core.responses import BaseResponse +from .exceptions import MQError +from .models import mq_backends + + +def error_handler(f): + @wraps(f) + def _wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except MQError as e: + return e.code, e.get_headers(), e.get_body() + + return _wrapper + + +class MQResponse(BaseResponse): + """Handler for MQ requests and responses.""" + + @property + def mq_backend(self): + """Return backend instance specific for this region.""" + return mq_backends[self.region] + + @error_handler + def broker(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.describe_broker() + if request.method == "DELETE": + return self.delete_broker() + if request.method == "PUT": + return self.update_broker() + + def brokers(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "POST": + return self.create_broker() + if request.method == "GET": + return self.list_brokers() + + @error_handler + def configuration(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.describe_configuration() + if request.method == "PUT": + return self.update_configuration() + + @error_handler + def configurations(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "POST": + return self.create_configuration() + if request.method == "GET": + return self.list_configurations() + + def configuration_revision(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.get_configuration_revision() + + def tags(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "POST": + return self.create_tags() + if request.method == "DELETE": + return self.delete_tags() + + @error_handler + def user(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "POST": + return self.create_user() + if request.method == "GET": + return self.describe_user() + if request.method == "PUT": + return self.update_user() + if request.method == "DELETE": + return self.delete_user() + + def users(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.list_users() + + def create_broker(self): + params = json.loads(self.body) + authentication_strategy = params.get("authenticationStrategy") + auto_minor_version_upgrade = params.get("autoMinorVersionUpgrade") + broker_name = params.get("brokerName") + configuration = params.get("configuration") + creator_request_id = params.get("creatorRequestId") + deployment_mode = params.get("deploymentMode") + encryption_options = params.get("encryptionOptions") + engine_type = params.get("engineType") + engine_version = params.get("engineVersion") + host_instance_type = params.get("hostInstanceType") + ldap_server_metadata = params.get("ldapServerMetadata") + logs = params.get("logs", {}) + maintenance_window_start_time = params.get("maintenanceWindowStartTime") + publicly_accessible = params.get("publiclyAccessible") + security_groups = params.get("securityGroups") + storage_type = params.get("storageType") + subnet_ids = params.get("subnetIds", []) + tags = params.get("tags") + users = params.get("users", []) + broker_arn, broker_id = self.mq_backend.create_broker( + authentication_strategy=authentication_strategy, + auto_minor_version_upgrade=auto_minor_version_upgrade, + broker_name=broker_name, + configuration=configuration, + creator_request_id=creator_request_id, + deployment_mode=deployment_mode, + encryption_options=encryption_options, + engine_type=engine_type, + engine_version=engine_version, + host_instance_type=host_instance_type, + ldap_server_metadata=ldap_server_metadata, + logs=logs, + maintenance_window_start_time=maintenance_window_start_time, + publicly_accessible=publicly_accessible, + security_groups=security_groups, + storage_type=storage_type, + subnet_ids=subnet_ids, + tags=tags, + users=users, + ) + # Lowercase members - boto3 will convert it into UpperCase + resp = {"brokerArn": broker_arn, "brokerId": broker_id} + return 200, {}, json.dumps(resp) + + def update_broker(self): + params = json.loads(self.body) + broker_id = self.path.split("/")[-1] + authentication_strategy = params.get("authenticationStrategy") + auto_minor_version_upgrade = params.get("autoMinorVersionUpgrade") + configuration = params.get("configuration") + engine_version = params.get("engineVersion") + host_instance_type = params.get("hostInstanceType") + ldap_server_metadata = params.get("ldapServerMetadata") + logs = params.get("logs") + maintenance_window_start_time = params.get("maintenanceWindowStartTime") + security_groups = params.get("securityGroups") + self.mq_backend.update_broker( + authentication_strategy=authentication_strategy, + auto_minor_version_upgrade=auto_minor_version_upgrade, + broker_id=broker_id, + configuration=configuration, + engine_version=engine_version, + host_instance_type=host_instance_type, + ldap_server_metadata=ldap_server_metadata, + logs=logs, + maintenance_window_start_time=maintenance_window_start_time, + security_groups=security_groups, + ) + return self.describe_broker() + + def delete_broker(self): + broker_id = self.path.split("/")[-1] + self.mq_backend.delete_broker(broker_id=broker_id) + return 200, {}, json.dumps(dict(brokerId=broker_id)) + + def describe_broker(self): + broker_id = self.path.split("/")[-1] + broker = self.mq_backend.describe_broker(broker_id=broker_id) + resp = broker.to_json() + resp["tags"] = self.mq_backend.list_tags(broker.arn) + return 200, {}, json.dumps(resp) + + def list_brokers(self): + brokers = self.mq_backend.list_brokers() + return 200, {}, json.dumps(dict(brokerSummaries=[b.summary() for b in brokers])) + + def create_user(self): + params = json.loads(self.body) + broker_id = self.path.split("/")[-3] + username = self.path.split("/")[-1] + console_access = params.get("consoleAccess", False) + groups = params.get("groups", []) + self.mq_backend.create_user(broker_id, username, console_access, groups) + return 200, {}, "{}" + + def update_user(self): + params = json.loads(self.body) + broker_id = self.path.split("/")[-3] + username = self.path.split("/")[-1] + console_access = params.get("consoleAccess", False) + groups = params.get("groups", []) + self.mq_backend.update_user( + broker_id=broker_id, + console_access=console_access, + groups=groups, + username=username, + ) + return 200, {}, "{}" + + def describe_user(self): + broker_id = self.path.split("/")[-3] + username = self.path.split("/")[-1] + user = self.mq_backend.describe_user(broker_id, username) + return 200, {}, json.dumps(user.to_json()) + + def delete_user(self): + broker_id = self.path.split("/")[-3] + username = self.path.split("/")[-1] + self.mq_backend.delete_user(broker_id, username) + return 200, {}, "{}" + + def list_users(self): + broker_id = self.path.split("/")[-2] + users = self.mq_backend.list_users(broker_id=broker_id) + resp = { + "brokerId": broker_id, + "users": [{"username": u.username} for u in users], + } + return 200, {}, json.dumps(resp) + + def create_configuration(self): + params = json.loads(self.body) + name = params.get("name") + engine_type = params.get("engineType") + engine_version = params.get("engineVersion") + tags = params.get("tags", {}) + + config = self.mq_backend.create_configuration( + name, engine_type, engine_version, tags + ) + return 200, {}, json.dumps(config.to_json()) + + def describe_configuration(self): + config_id = self.path.split("/")[-1] + config = self.mq_backend.describe_configuration(config_id) + resp = config.to_json() + resp["tags"] = self.mq_backend.list_tags(config.arn) + return 200, {}, json.dumps(resp) + + def list_configurations(self): + configs = self.mq_backend.list_configurations() + resp = {"configurations": [c.to_json() for c in configs]} + return 200, {}, json.dumps(resp) + + def update_configuration(self): + config_id = self.path.split("/")[-1] + params = json.loads(self.body) + data = params.get("data") + description = params.get("description") + config = self.mq_backend.update_configuration(config_id, data, description) + return 200, {}, json.dumps(config.to_json()) + + def get_configuration_revision(self): + revision_id = self.path.split("/")[-1] + config_id = self.path.split("/")[-3] + revision = self.mq_backend.describe_configuration_revision( + config_id, revision_id + ) + return 200, {}, json.dumps(revision.to_json()) + + def create_tags(self): + resource_arn = unquote(self.path.split("/")[-1]) + tags = json.loads(self.body).get("tags", {}) + self.mq_backend.create_tags(resource_arn, tags) + return 200, {}, "{}" + + def delete_tags(self): + resource_arn = unquote(self.path.split("/")[-1]) + tag_keys = self._get_param("tagKeys") + self.mq_backend.delete_tags(resource_arn, tag_keys) + return 200, {}, "{}" + + def reboot(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "POST": + broker_id = self.path.split("/")[-2] + self.mq_backend.reboot_broker(broker_id=broker_id) + return 200, {}, "{}" diff --git a/moto/mq/urls.py b/moto/mq/urls.py new file mode 100644 index 000000000..26cc6cd8d --- /dev/null +++ b/moto/mq/urls.py @@ -0,0 +1,22 @@ +"""mq base URL and path.""" +from .responses import MQResponse + +url_bases = [ + r"https?://mq\.(.+)\.amazonaws\.com", +] + + +response = MQResponse() + + +url_paths = { + "{0}/v1/brokers/(?P[^/]+)$": response.broker, + "{0}/v1/brokers/(?P[^/]+)/reboot$": response.reboot, + "{0}/v1/brokers/(?P[^/]+)/users$": response.users, + "{0}/v1/brokers/(?P[^/]+)/users/(?P[^/]+)$": response.user, + "{0}/v1/brokers$": response.brokers, + "{0}/v1/configurations$": response.configurations, + "{0}/v1/configurations/(?P[^/]+)$": response.configuration, + "{0}/v1/configurations/(?P[^/]+)/revisions/(?P[^/]+)$": response.configuration_revision, + "{0}/v1/tags/(?P[^/]+)$": response.tags, +} diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index ce6bbfd9a..a9b884435 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -68,6 +68,7 @@ TestAccAWSIAMUserPolicy TestAccAWSIPRanges TestAccAWSKmsAlias TestAccAWSKmsSecretDataSource +TestAccAWSMq TestAccAWSPartition TestAccAWSProvider TestAccAWSRedshiftServiceAccount diff --git a/tests/test_mq/__init__.py b/tests/test_mq/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_mq/test_mq.py b/tests/test_mq/test_mq.py new file mode 100644 index 000000000..370cc74d8 --- /dev/null +++ b/tests/test_mq/test_mq.py @@ -0,0 +1,427 @@ +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.exceptions import ClientError +from moto import mock_mq + +# 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_mq +def test_create_broker_minimal(): + client = boto3.client("mq", region_name="ap-southeast-1") + resp = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + ) + + resp.should.have.key("BrokerId") + resp.should.have.key("BrokerArn").match("arn:aws") + + +@mock_mq +def test_create_with_tags(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="CLUSTER_MULTI_AZ", + EngineType="RabbitMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Tags={"key1": "val2", "key2": "val2"}, + Users=[], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Tags").equals({"key1": "val2", "key2": "val2"}) + + +@mock_mq +def test_create_with_multiple_users(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="CLUSTER_MULTI_AZ", + EngineType="RabbitMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[ + { + "ConsoleAccess": True, + "Groups": ["second", "first", "third"], + "Password": "SecondTestTest1234", + "Username": "SecondTest", + }, + { + "ConsoleAccess": False, + "Groups": [], + "Password": "TestTest1234", + "Username": "Test", + }, + ], + )["BrokerId"] + + user1 = client.describe_user(BrokerId=broker_id, Username="SecondTest") + user1.should.have.key("Username").equals("SecondTest") + user1.should.have.key("Groups").equals(["second", "first", "third"]) + user1.should.have.key("ConsoleAccess").equals(True) + + user2 = client.describe_user(BrokerId=broker_id, Username="Test") + user2.should.have.key("Username").equals("Test") + user2.should.have.key("Groups").equals([]) + user2.should.have.key("ConsoleAccess").equals(False) + + +@mock_mq +def test_create_with_configuration(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + Configuration={"Id": "config_id_x", "Revision": 3}, + DeploymentMode="CLUSTER_SINGLE", + EngineType="ActiveMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Tags={"key1": "val2", "key2": "val2"}, + Users=[], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Configurations") + resp["Configurations"].should.have.key("Current").equals( + {"Id": "config_id_x", "Revision": 3} + ) + + +@mock_mq +def test_update_with_configuration(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + Configuration={"Id": "config_id_x", "Revision": 1}, + DeploymentMode="CLUSTER_SINGLE", + EngineType="ActiveMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Tags={"key1": "val2", "key2": "val2"}, + Users=[], + )["BrokerId"] + + client.update_broker( + BrokerId=broker_id, Configuration={"Id": "config_id_x", "Revision": 2} + ) + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Configurations") + resp["Configurations"].should.have.key("Current").equals( + {"Id": "config_id_x", "Revision": 2} + ) + + +@mock_mq +def test_delete_broker(): + client = boto3.client("mq", region_name="ap-southeast-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + + resp = client.delete_broker(BrokerId=broker_id) + resp.should.have.key("BrokerId").equals(broker_id) + + client.list_brokers().should.have.key("BrokerSummaries").length_of(0) + + +@mock_mq +def test_describe_broker(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AuthenticationStrategy="SIMPLE", + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EncryptionOptions={"KmsKeyId": "kms-key", "UseAwsOwnedKey": False}, + EngineType="ACTIVEMQ", + EngineVersion="version", + LdapServerMetadata={ + "Hosts": ["host1"], + "RoleBase": "role_base_thingy", + "RoleSearchMatching": "rsm", + "ServiceAccountUsername": "sau", + "ServiceAccountPassword": "sap", + "UserBase": "ub", + "UserSearchMatching": "usm", + }, + HostInstanceType="hit", + PubliclyAccessible=True, + SecurityGroups=["secgroup1"], + StorageType="efs", + SubnetIds=["s-id"], + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("BrokerArn").match("arn:aws") + resp.should.have.key("BrokerState").equals("RUNNING") + + resp.should.have.key("Created") + + resp.should.have.key("AuthenticationStrategy").equals("SIMPLE") + resp.should.have.key("AutoMinorVersionUpgrade").equals(False) + resp.should.have.key("BrokerName").equals("testbroker") + resp.should.have.key("DeploymentMode").equals("dm") + resp.should.have.key("EncryptionOptions").equals( + {"KmsKeyId": "kms-key", "UseAwsOwnedKey": False} + ) + resp.should.have.key("EngineType").equals("ACTIVEMQ") + resp.should.have.key("EngineVersion").equals("version") + resp.should.have.key("HostInstanceType").equals("hit") + resp.should.have.key("LdapServerMetadata").equals( + { + "Hosts": ["host1"], + "RoleBase": "role_base_thingy", + "RoleSearchMatching": "rsm", + "ServiceAccountUsername": "sau", + "UserBase": "ub", + "UserSearchMatching": "usm", + } + ) + resp.should.have.key("PubliclyAccessible").equals(True) + resp.should.have.key("SecurityGroups").equals(["secgroup1"]) + resp.should.have.key("StorageType").equals("efs") + resp.should.have.key("SubnetIds").equals(["s-id"]) + resp.should.have.key("Users").equals([{"Username": "admin"}]) + + +@mock_mq +def test_describe_broker_with_defaults(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("BrokerInstances").length_of(1) + + resp.should.have.key("Configurations") + resp["Configurations"].should.have.key("Current") + resp["Configurations"].should.have.key("History").length_of(0) + resp["Configurations"].shouldnt.have.key("Pending") + + resp.should.have.key("EncryptionOptions").equals({"UseAwsOwnedKey": True}) + + resp.should.have.key("MaintenanceWindowStartTime").equals( + {"DayOfWeek": "Sunday", "TimeOfDay": "00:00", "TimeZone": "UTC"} + ) + + resp.should.have.key("Logs").equals({"Audit": False, "General": False}) + + resp.should.have.key("SubnetIds").length_of(1) + + +@mock_mq +def test_describe_multiple_rabbits(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="CLUSTER_MULTI_AZ", + EngineType="RabbitMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("BrokerInstances") + resp["BrokerInstances"][0]["ConsoleURL"].should.equal( + "https://0000.mq.us-east-2.amazonaws.com" + ) + resp["BrokerInstances"][0]["Endpoints"].should.have.length_of(1) + resp.shouldnt.have.key("Configurations") + resp.should.have.key("Logs").equals({"General": False}) + resp.should.have.key("SubnetIds").length_of(4) + + +@mock_mq +def test_describe_active_mq_with_standby(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="ACTIVE_STANDBY_MULTI_AZ", + EngineType="ActiveMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + # Instances and subnets in two regions - one active, one standby + resp.should.have.key("BrokerInstances").length_of(2) + resp.should.have.key("SubnetIds").length_of(2) + + +@mock_mq +def test_describe_broker_unknown(): + client = boto3.client("mq", region_name="us-east-2") + + with pytest.raises(ClientError) as exc: + client.describe_broker(BrokerId="unknown") + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal( + "Can't find requested broker [unknown]. Make sure your broker exists." + ) + + +@mock_mq +def test_list_brokers_empty(): + client = boto3.client("mq", region_name="eu-west-1") + resp = client.list_brokers() + + resp.should.have.key("BrokerSummaries").equals([]) + + +@mock_mq +def test_list_brokers(): + client = boto3.client("mq", region_name="eu-west-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + + resp = client.list_brokers() + + resp.should.have.key("BrokerSummaries").length_of(1) + + summary = resp["BrokerSummaries"][0] + summary.should.have.key("BrokerArn") + summary.should.have.key("BrokerId").equals(broker_id) + summary.should.have.key("BrokerName").equals("testbroker") + summary.should.have.key("BrokerState").equals("RUNNING") + summary.should.have.key("Created") + summary.should.have.key("DeploymentMode").equals("dm") + summary.should.have.key("EngineType").equals("ACTIVEMQ") + summary.should.have.key("HostInstanceType").equals("hit") + + summary.shouldnt.have.key("Users") + + +@mock_mq +def test_update_broker_single_attribute(): + client = boto3.client("mq", region_name="ap-southeast-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + resp = client.update_broker(AutoMinorVersionUpgrade=True, BrokerId=broker_id) + + # Changed + resp.should.have.key("AutoMinorVersionUpgrade").equals(True) + + # Unchanged + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("EngineVersion").equals("version") + + +@mock_mq +def test_update_broker_multiple_attributes(): + client = boto3.client("mq", region_name="ap-southeast-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + SecurityGroups=["sg-1"], + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + resp = client.update_broker( + AutoMinorVersionUpgrade=True, + BrokerId=broker_id, + Logs={"Audit": True, "General": True}, + EngineVersion="version2", + SecurityGroups=["sg-1", "sg-2"], + ) + + # Changed + resp.should.have.key("AutoMinorVersionUpgrade").equals(True) + resp.should.have.key("Logs").equals({"Audit": True, "General": True}) + resp.should.have.key("EngineVersion").equals("version2") + resp.should.have.key("SecurityGroups").equals(["sg-1", "sg-2"]) + + # Unchanged + resp.should.have.key("BrokerId").equals(broker_id) + + +@mock_mq +def test_reboot_broker(): + client = boto3.client("mq", region_name="ap-southeast-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + client.reboot_broker(BrokerId=broker_id) + + # Noop - nothing to assert or verify + pass diff --git a/tests/test_mq/test_mq_configuration.py b/tests/test_mq/test_mq_configuration.py new file mode 100644 index 000000000..cefbe5fac --- /dev/null +++ b/tests/test_mq/test_mq_configuration.py @@ -0,0 +1,210 @@ +"""Unit tests for mq-supported APIs.""" +import base64 + +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.exceptions import ClientError +from moto import mock_mq +from moto.core import ACCOUNT_ID + +# 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_mq +def test_create_configuration_minimal(): + client = boto3.client("mq", region_name="ap-southeast-1") + resp = client.create_configuration( + EngineType="ACTIVEMQ", EngineVersion="rabbit1", Name="myconfig" + ) + + resp.should.have.key("Id").match("^c-") + resp.should.have.key("Arn").equals( + f"arn:aws:mq:ap-southeast-1:{ACCOUNT_ID}:configuration:{resp['Id']}" + ) + resp.should.have.key("AuthenticationStrategy").equals("simple") + resp.should.have.key("Created") + resp.should.have.key("Name").equals("myconfig") + resp.should.have.key("LatestRevision") + + revision = resp["LatestRevision"] + revision.should.have.key("Created") + revision.should.have.key("Description") + revision.should.have.key("Revision").equals(1) + + +@mock_mq +def test_create_configuration_for_rabbitmq(): + client = boto3.client("mq", region_name="us-east-1") + + with pytest.raises(ClientError) as exc: + client.create_configuration( + EngineType="RABBITMQ", EngineVersion="rabbit1", Name="myconfig" + ) + err = exc.value.response["Error"] + err["Code"].should.equal("BadRequestException") + err["Message"].should.equal( + "Broker engine type [RABBITMQ] does not support configuration." + ) + + +@mock_mq +def test_create_configuration_for_unknown_engine(): + client = boto3.client("mq", region_name="us-east-1") + + with pytest.raises(ClientError) as exc: + client.create_configuration( + EngineType="unknown", EngineVersion="rabbit1", Name="myconfig" + ) + err = exc.value.response["Error"] + err["Code"].should.equal("BadRequestException") + err["Message"].should.equal( + "Broker engine type [unknown] is invalid. Valid values are: [ACTIVEMQ]" + ) + + +@mock_mq +def test_describe_configuration(): + client = boto3.client("mq", region_name="eu-north-1") + config_id = client.create_configuration( + EngineType="ACTIVEMQ", EngineVersion="active2", Name="myconfig" + )["Id"] + + resp = client.describe_configuration(ConfigurationId=config_id) + + resp.should.have.key("Id").match("^c-") + resp.should.have.key("Arn").equals( + f"arn:aws:mq:eu-north-1:{ACCOUNT_ID}:configuration:{resp['Id']}" + ) + resp.should.have.key("AuthenticationStrategy").equals("simple") + resp.should.have.key("Created") + resp.should.have.key("Name").equals("myconfig") + resp.should.have.key("LatestRevision") + + revision = resp["LatestRevision"] + revision.should.have.key("Created") + revision.should.have.key("Description") + revision.should.have.key("Revision").equals(1) + + +@mock_mq +def test_describe_configuration_revision(): + client = boto3.client("mq", region_name="eu-north-1") + config_id = client.create_configuration( + EngineType="ActiveMQ", EngineVersion="5.16.3", Name="myconfig" + )["Id"] + + resp = client.describe_configuration_revision( + ConfigurationId=config_id, ConfigurationRevision="1" + ) + + resp.should.have.key("ConfigurationId").equals(config_id) + resp.should.have.key("Created") + resp.should.have.key("Description").equals( + "Auto-generated default for myconfig on ActiveMQ 5.16.3" + ) + + resp.should.have.key("Data") + + +@mock_mq +def test_describe_configuration_unknown(): + client = boto3.client("mq", region_name="us-east-2") + + with pytest.raises(ClientError) as exc: + client.describe_configuration(ConfigurationId="c-unknown") + err = exc.value.response["Error"] + + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal( + "Can't find requested configuration [c-unknown]. Make sure your configuration exists." + ) + + +@mock_mq +def test_list_configurations_empty(): + client = boto3.client("mq", region_name="us-east-2") + + resp = client.list_configurations() + + resp.should.have.key("Configurations").equals([]) + + +@mock_mq +def test_list_configurations(): + client = boto3.client("mq", region_name="ap-southeast-1") + config_id = client.create_configuration( + EngineType="ACTIVEMQ", EngineVersion="active1", Name="myconfig" + )["Id"] + + resp = client.list_configurations() + + resp.should.have.key("Configurations").length_of(1) + + config = resp["Configurations"][0] + config.should.have.key("Arn").match("arn:aws") + config.should.have.key("Created") + config.should.have.key("Id").equals(config_id) + config.should.have.key("Name").equals("myconfig") + config.should.have.key("EngineType").equals("ACTIVEMQ") + config.should.have.key("EngineVersion").equals("active1") + + +@mock_mq +def test_update_configuration(): + client = boto3.client("mq", region_name="ap-southeast-1") + config_id = client.create_configuration( + EngineType="ACTIVEMQ", EngineVersion="rabbit1", Name="myconfig" + )["Id"] + + resp = client.update_configuration( + ConfigurationId=config_id, + Data="base64encodedxmlconfig", + Description="updated config", + ) + + resp.should.have.key("Arn").match("arn:aws:mq") + resp.should.have.key("Created") + resp.should.have.key("Id") + resp.should.have.key("Name").equals("myconfig") + resp.should.have.key("LatestRevision") + + revision = resp["LatestRevision"] + revision.should.have.key("Created") + revision.should.have.key("Description").equals("updated config") + revision.should.have.key("Revision").equals(2) + + +@mock_mq +def test_update_configuration_to_ldap(): + client = boto3.client("mq", region_name="ap-southeast-1") + config_id = client.create_configuration( + EngineType="ACTIVEMQ", EngineVersion="rabbit1", Name="myconfig" + )["Id"] + + ldap_config = """ + + + + + + + + + + + + +""" + + client.update_configuration( + ConfigurationId=config_id, + Data=base64.b64encode(ldap_config.encode("utf-8")).decode("utf-8"), + Description="update config to use LDAP authorization", + ) + + resp = client.describe_configuration(ConfigurationId=config_id) + + resp.should.have.key("AuthenticationStrategy").equals("ldap") diff --git a/tests/test_mq/test_mq_tags.py b/tests/test_mq/test_mq_tags.py new file mode 100644 index 000000000..ccf109834 --- /dev/null +++ b/tests/test_mq/test_mq_tags.py @@ -0,0 +1,109 @@ +import boto3 +import sure # noqa # pylint: disable=unused-import + +from moto import mock_mq + + +@mock_mq +def test_create_broker_with_tags(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="CLUSTER_MULTI_AZ", + EngineType="RabbitMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Tags={"key1": "val2", "key2": "val2"}, + Users=[], + )["BrokerId"] + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Tags").equals({"key1": "val2", "key2": "val2"}) + + +@mock_mq +def test_create_tags(): + client = boto3.client("mq", region_name="us-east-2") + resp = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="CLUSTER_MULTI_AZ", + EngineType="RabbitMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + ) + + broker_arn = resp["BrokerArn"] + broker_id = resp["BrokerId"] + + client.create_tags(ResourceArn=broker_arn, Tags={"key1": "val2", "key2": "val2"}) + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Tags").equals({"key1": "val2", "key2": "val2"}) + + +@mock_mq +def test_delete_tags(): + client = boto3.client("mq", region_name="us-east-2") + resp = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="CLUSTER_MULTI_AZ", + EngineType="RabbitMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + ) + + broker_arn = resp["BrokerArn"] + broker_id = resp["BrokerId"] + + client.create_tags(ResourceArn=broker_arn, Tags={"key1": "val2", "key2": "val2"}) + + client.delete_tags(ResourceArn=broker_arn, TagKeys=["key1"]) + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Tags").equals({"key2": "val2"}) + + +@mock_mq +def test_create_configuration_with_tags(): + client = boto3.client("mq", region_name="ap-southeast-1") + resp = client.create_configuration( + EngineType="ACTIVEMQ", + EngineVersion="rabbit1", + Name="myconfig", + Tags={"key1": "val1", "key2": "val2"}, + ) + + # The CreateConfiguration call does not return tags + resp.shouldnt.have.key("Tags") + + # Only when describing will they be returned + resp = client.describe_configuration(ConfigurationId=resp["Id"]) + resp.should.have.key("Tags").equals({"key1": "val1", "key2": "val2"}) + + +@mock_mq +def test_add_tags_to_configuration(): + client = boto3.client("mq", region_name="ap-southeast-1") + resp = client.create_configuration( + EngineType="ACTIVEMQ", + EngineVersion="rabbit1", + Name="myconfig", + Tags={"key1": "val1", "key2": "val2"}, + ) + + client.create_tags(ResourceArn=resp["Arn"], Tags={"key1": "val1", "key2": "val2"}) + + # Only when describing will they be returned + resp = client.describe_configuration(ConfigurationId=resp["Id"]) + resp.should.have.key("Tags").equals({"key1": "val1", "key2": "val2"}) diff --git a/tests/test_mq/test_mq_users.py b/tests/test_mq/test_mq_users.py new file mode 100644 index 000000000..4c3f04099 --- /dev/null +++ b/tests/test_mq/test_mq_users.py @@ -0,0 +1,172 @@ +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.exceptions import ClientError +from moto import mock_mq + + +@mock_mq +def test_create_user(): + client = boto3.client("mq", region_name="us-east-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + client.create_user(BrokerId=broker_id, Username="admin", Password="adm1n") + + resp = client.describe_broker(BrokerId=broker_id) + + resp.should.have.key("Users").equals([{"Username": "admin"}]) + + +@mock_mq +def test_describe_user(): + client = boto3.client("mq", region_name="us-east-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + client.create_user( + BrokerId=broker_id, + Username="admin", + Password="adm1n", + ConsoleAccess=True, + Groups=["group1", "group2"], + ) + + resp = client.describe_user(BrokerId=broker_id, Username="admin") + + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("ConsoleAccess").equals(True) + resp.should.have.key("Groups").equals(["group1", "group2"]) + resp.should.have.key("Username").equals("admin") + + +@mock_mq +def test_describe_user_unknown(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + with pytest.raises(ClientError) as exc: + client.describe_user(BrokerId=broker_id, Username="unknown") + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal( + "Can't find requested user [unknown]. Make sure your user exists." + ) + + +@mock_mq +def test_list_users_empty(): + client = boto3.client("mq", region_name="us-east-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[], + )["BrokerId"] + + resp = client.list_users(BrokerId=broker_id) + + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("Users").equals([]) + + +@mock_mq +def test_list_users(): + client = boto3.client("mq", region_name="us-east-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + + client.create_user(BrokerId=broker_id, Username="user1", Password="us3r1") + + resp = client.list_users(BrokerId=broker_id) + + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("Users").length_of(2) + resp["Users"].should.contain({"Username": "admin"}) + resp["Users"].should.contain({"Username": "user1"}) + + +@mock_mq +def test_update_user(): + client = boto3.client("mq", region_name="us-east-2") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + + client.update_user(BrokerId=broker_id, Username="admin", Groups=["administrators"]) + + resp = client.describe_user(BrokerId=broker_id, Username="admin") + + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("Groups").equals(["administrators"]) + resp.should.have.key("Username").equals("admin") + + +@mock_mq +def test_delete_user(): + client = boto3.client("mq", region_name="us-east-1") + broker_id = client.create_broker( + AutoMinorVersionUpgrade=False, + BrokerName="testbroker", + DeploymentMode="dm", + EngineType="ACTIVEMQ", + EngineVersion="version", + HostInstanceType="hit", + PubliclyAccessible=True, + Users=[{"Username": "admin", "Password": "adm1n"}], + )["BrokerId"] + + client.create_user(BrokerId=broker_id, Username="user1", Password="us3r1") + + client.delete_user(BrokerId=broker_id, Username="admin") + + resp = client.list_users(BrokerId=broker_id) + + resp.should.have.key("BrokerId").equals(broker_id) + resp.should.have.key("Users").length_of(1) + resp["Users"].should.contain({"Username": "user1"}) diff --git a/tests/test_mq/test_server.py b/tests/test_mq/test_server.py new file mode 100644 index 000000000..b667e1145 --- /dev/null +++ b/tests/test_mq/test_server.py @@ -0,0 +1,15 @@ +import json +import sure # noqa # pylint: disable=unused-import + +from moto import mock_mq +import moto.server as server + + +@mock_mq +def test_mq_list(): + backend = server.create_backend_app("mq") + test_client = backend.test_client() + + resp = test_client.get("/v1/brokers") + resp.status_code.should.equal(200) + json.loads(resp.data).should.equal({"brokerSummaries": []})