Repository: getmoto/moto

Issue:
Glue: support for start_crawler, stop_crawler
https://github.com/spulec/moto/issues/4212 does some of ground work for some of the Glue APIs.

We use moto for mocking out other AWS integrations but currently `start_crawler` and `stop_crawler` aren't supported by moto.

Will raise a PR shortly. Creating an issue based on the approach mentioned in [contributing.md](https://github.com/spulec/moto/blob/6f5cae98ad3e2b2e1c600fdd7b2ca465fafb71a3/CONTRIBUTING.md)




Discussion:

Resolving patch:
diff --git a/moto/glue/exceptions.py b/moto/glue/exceptions.py
--- a/moto/glue/exceptions.py
+++ b/moto/glue/exceptions.py
@@ -62,3 +62,15 @@ def __init__(self, crawler):
 class VersionNotFoundException(EntityNotFoundException):
     def __init__(self):
         super(VersionNotFoundException, self).__init__("Version not found.")
+
+
+class CrawlerRunningException(GlueClientError):
+    def __init__(self, msg):
+        super(CrawlerRunningException, self).__init__("CrawlerRunningException", msg)
+
+
+class CrawlerNotRunningException(GlueClientError):
+    def __init__(self, msg):
+        super(CrawlerNotRunningException, self).__init__(
+            "CrawlerNotRunningException", msg
+        )
diff --git a/moto/glue/models.py b/moto/glue/models.py
--- a/moto/glue/models.py
+++ b/moto/glue/models.py
@@ -1,8 +1,10 @@
 import time
+from collections import OrderedDict
 from datetime import datetime
 
 from moto.core import BaseBackend, BaseModel
-from collections import OrderedDict
+from moto.glue.exceptions import CrawlerRunningException, CrawlerNotRunningException
+
 from .exceptions import (
     JsonRESTError,
     CrawlerAlreadyExistsException,
@@ -122,6 +124,14 @@ def get_crawler(self, name):
     def get_crawlers(self):
         return [self.crawlers[key] for key in self.crawlers] if self.crawlers else []
 
+    def start_crawler(self, name):
+        crawler = self.get_crawler(name)
+        crawler.start_crawler()
+
+    def stop_crawler(self, name):
+        crawler = self.get_crawler(name)
+        crawler.stop_crawler()
+
     def delete_crawler(self, name):
         try:
             del self.crawlers[name]
@@ -311,6 +321,20 @@ def as_dict(self):
 
         return data
 
+    def start_crawler(self):
+        if self.state == "RUNNING":
+            raise CrawlerRunningException(
+                f"Crawler with name {self.name} has already started"
+            )
+        self.state = "RUNNING"
+
+    def stop_crawler(self):
+        if self.state != "RUNNING":
+            raise CrawlerNotRunningException(
+                f"Crawler with name {self.name} isn't running"
+            )
+        self.state = "STOPPING"
+
 
 class LastCrawlInfo(BaseModel):
     def __init__(
diff --git a/moto/glue/responses.py b/moto/glue/responses.py
--- a/moto/glue/responses.py
+++ b/moto/glue/responses.py
@@ -303,6 +303,16 @@ def get_crawlers(self):
         crawlers = self.glue_backend.get_crawlers()
         return json.dumps({"Crawlers": [crawler.as_dict() for crawler in crawlers]})
 
+    def start_crawler(self):
+        name = self.parameters.get("Name")
+        self.glue_backend.start_crawler(name)
+        return ""
+
+    def stop_crawler(self):
+        name = self.parameters.get("Name")
+        self.glue_backend.stop_crawler(name)
+        return ""
+
     def delete_crawler(self):
         name = self.parameters.get("Name")
         self.glue_backend.delete_crawler(name)


Test patch:
diff --git a/tests/test_glue/test_datacatalog.py b/tests/test_glue/test_datacatalog.py
--- a/tests/test_glue/test_datacatalog.py
+++ b/tests/test_glue/test_datacatalog.py
@@ -1117,6 +1117,60 @@ def test_get_crawlers_several_items():
     crawlers[1].get("Name").should.equal(name_2)
 
 
+@mock_glue
+def test_start_crawler():
+    client = boto3.client("glue", region_name="us-east-1")
+    name = "my_crawler_name"
+    helpers.create_crawler(client, name)
+
+    client.start_crawler(Name=name)
+
+    response = client.get_crawler(Name=name)
+    crawler = response["Crawler"]
+
+    crawler.get("State").should.equal("RUNNING")
+
+
+@mock_glue
+def test_start_crawler_should_raise_exception_if_already_running():
+    client = boto3.client("glue", region_name="us-east-1")
+    name = "my_crawler_name"
+    helpers.create_crawler(client, name)
+
+    client.start_crawler(Name=name)
+    with pytest.raises(ClientError) as exc:
+        client.start_crawler(Name=name)
+
+    exc.value.response["Error"]["Code"].should.equal("CrawlerRunningException")
+
+
+@mock_glue
+def test_stop_crawler():
+    client = boto3.client("glue", region_name="us-east-1")
+    name = "my_crawler_name"
+    helpers.create_crawler(client, name)
+    client.start_crawler(Name=name)
+
+    client.stop_crawler(Name=name)
+
+    response = client.get_crawler(Name=name)
+    crawler = response["Crawler"]
+
+    crawler.get("State").should.equal("STOPPING")
+
+
+@mock_glue
+def test_stop_crawler_should_raise_exception_if_not_running():
+    client = boto3.client("glue", region_name="us-east-1")
+    name = "my_crawler_name"
+    helpers.create_crawler(client, name)
+
+    with pytest.raises(ClientError) as exc:
+        client.stop_crawler(Name=name)
+
+    exc.value.response["Error"]["Code"].should.equal("CrawlerNotRunningException")
+
+
 @mock_glue
 def test_delete_crawler():
     client = boto3.client("glue", region_name="us-east-1")