Begin addition of snapshot support
diff --git a/ec2stack/controllers/default.py b/ec2stack/controllers/default.py
index d0b05bd..4371c96 100644
--- a/ec2stack/controllers/default.py
+++ b/ec2stack/controllers/default.py
@@ -8,7 +8,7 @@
 from ec2stack.core import Ec2stackError
 from ec2stack.services import USERS
 from ec2stack.providers.cloudstack import images, instances, keypairs, \
-    passwords, security_groups, zones, volumes, tags, vpcs
+    passwords, security_groups, zones, volumes, tags, vpcs, snapshots
 
 
 DEFAULT = Blueprint('default', __name__)
@@ -45,11 +45,13 @@
         security_groups.authenticate_security_group_ingress,
         'CreateKeyPair': keypairs.create_keypair,
         'CreateSecurityGroup': security_groups.create_security_group,
+        'CreateSnapshot': snapshots.create_snapshot,
         'CreateTags': tags.create_tags,
         'CreateVolume': volumes.create_volume,
         'CreateVpc': vpcs.create_vpc,
         'DeleteKeyPair': keypairs.delete_keypair,
         'DeleteSecurityGroup': security_groups.delete_security_group,
+        'DeleteSnapshot': snapshots.delete_snapshot,
         'DeleteTags': tags.delete_tags,
         'DeleteVolume': volumes.delete_volume,
         'DeleteVpc': vpcs.delete_vpc,
@@ -60,6 +62,7 @@
         'DescribeInstances': instances.describe_instances,
         'DescribeKeyPairs': keypairs.describe_keypairs,
         'DescribeSecurityGroups': security_groups.describe_security_groups,
+        'DescribeSnapshots': snapshots.describe_snapshots,
         'DescribeTags': tags.describe_tags,
         'DescribeVolumes': volumes.describe_volumes,
         'DescribeVpcs': vpcs.describe_vpcs,
diff --git a/ec2stack/providers/cloudstack/snapshots.py b/ec2stack/providers/cloudstack/snapshots.py
new file mode 100644
index 0000000..a75a5fb
--- /dev/null
+++ b/ec2stack/providers/cloudstack/snapshots.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+"""This module contains functions for handling requests in relation to snapshots.
+"""
+
+import uuid
+
+from flask import current_app
+
+from ec2stack import errors
+from ec2stack import helpers
+from ec2stack.providers import cloudstack
+from ec2stack.providers.cloudstack import requester, zones
+
+
+@helpers.authentication_required
+def create_snapshot():
+    """
+    Create a snapshot.
+
+    @return: Response.
+    """
+    response = _create_snapshot_request()
+    return _create_snapshot_response(response)
+
+
+def _create_snapshot_request():
+    """
+    Request to create a snapshot.
+
+    @return: Response.
+    """
+    args = {'command': 'createSnapshot'}
+
+    response = requester.make_request_async(args)
+
+    return response
+
+
+def _create_snapshot_response(response):
+    """
+    Generates a response for create snapshot request.
+
+    @param response: Response from Cloudstack.
+    @return: Response.
+    """
+
+    response = response['snapshot']
+    return {
+        'template_name_or_list': 'create_snapshot.xml',
+        'response_type': 'CreateSnapshotResponse',
+        'response': response
+    }
+
+
+@helpers.authentication_required
+def delete_snapshot():
+    """
+    Delete a snapshot.
+
+    @return: Response.
+    """
+    helpers.require_parameters(['SnapshotId'])
+    _delete_snapshot_request()
+    return _delete_snapshot_response()
+
+
+def _delete_snapshot_request():
+    """
+    Request to delete a snapshot.
+
+    @return: Response.
+    """
+    args = {'command': 'deleteSnapshot', 'id': helpers.get('SnapshotId')}
+
+    response = requester.make_request_async(args)
+
+    return response
+
+
+def _delete_snapshot_response():
+    """
+    Generates a response for delete snapshot request.
+
+    @return: Response.
+    """
+    return {
+        'template_name_or_list': 'status.xml',
+        'response_type': 'DeleteSnapshotResponse',
+        'return': 'true'
+    }
+
+
+@helpers.authentication_required
+def describe_snapshots():
+    """
+    Describes a specific snapshot or all snapshots.
+
+    @return: Response.
+    """
+    args = {'command': 'listSnapshots'}
+    response = cloudstack.describe_item(
+        args, 'snapshot', errors.invalid_snapshot_id, 'SnapshotId'
+    )
+
+    return _describe_snapshot_response(
+        response
+    )
+
+
+def _describe_snapshot_response(response):
+    """
+    Generates a response for describe snapshot request.
+
+    @param response: Response from Cloudstack.
+    @return: Response.
+    """
+    return {
+        'template_name_or_list': 'snapshots.xml',
+        'response_type': 'DescribeSnapshotsResponse',
+        'response': response
+    }
diff --git a/ec2stack/templates/create_snapshot.xml b/ec2stack/templates/create_snapshot.xml
new file mode 100644
index 0000000..b7f6ce0
--- /dev/null
+++ b/ec2stack/templates/create_snapshot.xml
@@ -0,0 +1,8 @@
+{% extends "response.xml" %}
+{% block response_content %}
+   <snapshot>
+      <snapshotId>{{ response.id }}</snapshotId>
+      <volumeId>{{ snapshot.volumeid }}</volumeId>
+      <status>{{ response.state }}</status>
+   </snapshot>
+{% endblock %}
\ No newline at end of file
diff --git a/ec2stack/templates/snapshots.xml b/ec2stack/templates/snapshots.xml
new file mode 100644
index 0000000..d4a3cee
--- /dev/null
+++ b/ec2stack/templates/snapshots.xml
@@ -0,0 +1,12 @@
+{% extends "response.xml" %}
+{% block response_content %}
+    <snapshotSet>
+        {% for snapshot in response.snapshot %}
+        <item>
+            <snapshotId>{{ snapshot.id }}</snapshotId>
+            <volumeId>{{ snapshot.volumeid }}</volumeId>
+            <status>{{ response.state }}</status>
+        </item>
+        {% endfor %}
+    </snapshotSet>
+{% endblock %}
\ No newline at end of file
diff --git a/tests/data/valid_create_snapshot.json b/tests/data/valid_create_snapshot.json
new file mode 100644
index 0000000..f8661cd
--- /dev/null
+++ b/tests/data/valid_create_snapshot.json
@@ -0,0 +1,25 @@
+{
+    "queryasyncjobresultresponse": {
+        "accountid": "f7ca69ba-1cd5-11e4-b589-080027ce083d",
+        "userid": "f7ca8d3c-1cd5-11e4-b589-080027ce083d",
+        "cmd": "org.apache.cloudstack.api.command.user.vmsnapshot.CreateVMSnapshotCmd",
+        "jobstatus": 1,
+        "jobprocstatus": 0,
+        "jobresultcode": 0,
+        "jobresulttype": "object",
+        "jobresult": {
+            "vmsnapshot": {
+                "id": "e0e04b84-6178-49a7-9d2f-a2cc6a82d4e4",
+                "name": "i-2-12-VM_VS_20140805203303",
+                "state": "Ready",
+                "displayname": "testsnapshot",
+                "virtualmachineid": "daa492b4-bd09-46b0-a4ad-142e187ecdbe",
+                "current": true,
+                "type": "Disk",
+                "created": "2014-08-05T21:33:04+0100"
+            }
+        },
+        "created": "2014-08-05T21:33:04+0100",
+        "jobid": "8f89700a-6bd0-4b4b-a3f9-272fff1e8fc7"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/valid_delete_snapshot.json b/tests/data/valid_delete_snapshot.json
new file mode 100644
index 0000000..681b102
--- /dev/null
+++ b/tests/data/valid_delete_snapshot.json
@@ -0,0 +1,16 @@
+{
+    "queryasyncjobresultresponse": {
+        "accountid": "f7ca69ba-1cd5-11e4-b589-080027ce083d",
+        "userid": "f7ca8d3c-1cd5-11e4-b589-080027ce083d",
+        "cmd": "org.apache.cloudstack.api.command.user.vmsnapshot.DeleteVMSnapshotCmd",
+        "jobstatus": 1,
+        "jobprocstatus": 0,
+        "jobresultcode": 0,
+        "jobresulttype": "object",
+        "jobresult": {
+            "success": true
+        },
+        "created": "2014-08-05T21:37:50+0100",
+        "jobid": "f94a67b2-e389-4790-ae89-f19968d31318"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/valid_describe_snapshot.json b/tests/data/valid_describe_snapshot.json
new file mode 100644
index 0000000..b4888a2
--- /dev/null
+++ b/tests/data/valid_describe_snapshot.json
@@ -0,0 +1,16 @@
+{
+    "listvmsnapshotresponse": {
+        "count": 1,
+        "vmSnapshot": [{
+            "id": "examplesnapshot",
+            "name": "i-2-12-VM_VS_20140805203432",
+            "state": "Ready",
+            "displayname": "testsnapshot1",
+            "virtualmachineid": "daa492b4-bd09-46b0-a4ad-142e187ecdbe",
+            "parentName": "testsnapshot",
+            "current": true,
+            "type": "Disk",
+            "created": "2014-08-05T21:34:32+0100"
+        }]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/valid_describe_snapshots.json b/tests/data/valid_describe_snapshots.json
new file mode 100644
index 0000000..6753646
--- /dev/null
+++ b/tests/data/valid_describe_snapshots.json
@@ -0,0 +1,25 @@
+{
+    "listvmsnapshotresponse": {
+        "count": 2,
+        "vmSnapshot": [{
+            "id": "9f2f067f-3ae7-427d-95d9-3c338dcbd5a3",
+            "name": "i-2-12-VM_VS_20140805203432",
+            "state": "Ready",
+            "displayname": "testsnapshot1",
+            "virtualmachineid": "daa492b4-bd09-46b0-a4ad-142e187ecdbe",
+            "parentName": "testsnapshot",
+            "current": true,
+            "type": "Disk",
+            "created": "2014-08-05T21:34:32+0100"
+        }, {
+            "id": "e0e04b84-6178-49a7-9d2f-a2cc6a82d4e4",
+            "name": "i-2-12-VM_VS_20140805203303",
+            "state": "Ready",
+            "displayname": "testsnapshot",
+            "virtualmachineid": "daa492b4-bd09-46b0-a4ad-142e187ecdbe",
+            "current": false,
+            "type": "Disk",
+            "created": "2014-08-05T21:33:04+0100"
+        }]
+    }
+}
\ No newline at end of file
diff --git a/tests/snapshot_tests.py b/tests/snapshot_tests.py
new file mode 100644
index 0000000..a100252
--- /dev/null
+++ b/tests/snapshot_tests.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from base64 import b64encode
+
+import mock
+import json
+
+from ec2stack.helpers import read_file, generate_signature
+from . import Ec2StackAppTestCase
+
+
+class SnapshotTestCase(Ec2StackAppTestCase):
+
+    def test_create_snapshot(self):
+        data = self.get_example_data()
+        data['Action'] = 'CreateSnapshot'
+        data['VolumeId'] = 'daa492b4-bd09-46b0-a4ad-142e187ecdbe'
+        data['Signature'] = generate_signature(data, 'POST', 'localhost', '/')
+
+        get = mock.Mock()
+        get.return_value.text = read_file(
+            'tests/data/valid_create_snapshot.json'
+        )
+        get.return_value.status_code = 200
+
+        with mock.patch('requests.get', get):
+            response = self.post(
+                '/',
+                data=data
+            )
+
+        self.assert_ok(response)
+        assert 'CreateSnapshotResponse' in response.data
+
+    def test_describe_snapshots(self):
+        data = self.get_example_data()
+        data['Action'] = 'DescribeSnapshots'
+        data['Signature'] = generate_signature(data, 'POST', 'localhost', '/')
+
+        get = mock.Mock()
+        get.return_value.text = read_file(
+            'tests/data/valid_describe_snapshots.json'
+        )
+        get.return_value.status_code = 200
+
+        with mock.patch('requests.get', get):
+            response = self.post(
+                '/',
+                data=data
+            )
+
+        self.assert_ok(response)
+        assert 'DescribeSnapshotsResponse' in response.data
+
+    def test_describe_snapshot_by_name(self):
+        data = self.get_example_data()
+        data['Action'] = 'DescribeVpcs'
+        data['SnapshotId'] = 'examplesnapshot'
+        data['Signature'] = generate_signature(data, 'POST', 'localhost', '/')
+
+        get = mock.Mock()
+        get.return_value.text = read_file(
+            'tests/data/valid_describe_snapshot.json'
+        )
+        get.return_value.status_code = 200
+
+        with mock.patch('requests.get', get):
+            response = self.post(
+                '/',
+                data=data
+            )
+
+        self.assert_ok(response)
+        assert 'DescribeSnapshotsResponse' in response.data
+        assert 'examplesnapshot' in response.data
+
+    def test_describe_snapshot_by_name_invalid_name(self):
+        data = self.get_example_data()
+        data['Action'] = 'DescribeVpcs'
+        data['SnapshotId'] = 'invalidsnapshot'
+        data['Signature'] = generate_signature(data, 'POST', 'localhost', '/')
+
+        get = mock.Mock()
+        get.return_value.text = read_file(
+            'tests/data/valid_describe_snapshot.json'
+        )
+        get.return_value.status_code = 200
+
+        with mock.patch('requests.get', get):
+            response = self.post(
+                '/',
+                data=data
+            )
+
+        self.assert_bad_request(response)
+        assert 'InvalidSnapshotID.NotFound' in response.data
+
+    def test_delete_snapshot(self):
+        data = self.get_example_data()
+        data['Action'] = 'DeleteSnapshot'
+        data['SnapshotId'] = 'Test'
+        data['Signature'] = generate_signature(data, 'POST', 'localhost', '/')
+
+        get = mock.Mock()
+        get.return_value.text = read_file(
+            'tests/data/valid_delete_snapshot.json'
+        )
+        get.return_value.status_code = 200
+
+        with mock.patch('requests.get', get):
+            response = self.post(
+                '/',
+                data=data
+            )
+
+        self.assert_ok(response)
+        assert 'DeleteSnapshotResponse' in response.data
\ No newline at end of file