diff --git a/moto/ec2/models.py b/moto/ec2/models.py index b3fe3f699..ab13661eb 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -3,7 +3,7 @@ from collections import defaultdict from boto.ec2.instance import Instance, InstanceState, Reservation from moto.core import BaseBackend -from .utils import random_instance_id, random_reservation_id, random_ami_id +from .utils import random_instance_id, random_reservation_id, random_ami_id, random_security_group_id class InstanceBackend(object): @@ -133,7 +133,7 @@ class AmiBackend(object): def create_image(self, instance_id, name, description): # TODO: check that instance exists and pull info from it. ami_id = random_ami_id() - instance = ec2_backend.get_instance(instance_id) + instance = self.get_instance(instance_id) if not instance: return None ami = Ami(ami_id, instance, name, description) @@ -193,7 +193,46 @@ class RegionsAndZonesBackend(object): return self.zones -class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, RegionsAndZonesBackend): +class SecurityGroup(object): + def __init__(self, group_id, name, description): + self.id = group_id + self.name = name + self.description = description + + +class SecurityGroupBackend(object): + + def __init__(self): + self.groups = {} + + def create_security_group(self, name, description): + group_id = random_security_group_id() + existing_group = self.get_security_group_from_name(name) + if existing_group: + return None + group = SecurityGroup(group_id, name, description) + self.groups[group_id] = group + return group + + def describe_security_groups(self): + return self.groups.values() + + def delete_security_group(self, name_or_group_id): + if name_or_group_id in self.groups: + # Group Id + return self.groups.pop(name_or_group_id) + else: + # Group Name + group = self.get_security_group_from_name(name_or_group_id) + if group: + return self.groups.pop(group.id) + + def get_security_group_from_name(self, name): + for group_id, group in self.groups.iteritems(): + if group.name == name: + return group + +class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, RegionsAndZonesBackend, SecurityGroupBackend): pass diff --git a/moto/ec2/responses/__init__.py b/moto/ec2/responses/__init__.py index c67c3e30e..7da82d3f6 100644 --- a/moto/ec2/responses/__init__.py +++ b/moto/ec2/responses/__init__.py @@ -35,7 +35,7 @@ from .tags import TagResponse class EC2Response(object): - sub_responses = [InstanceResponse, TagResponse, AmisResponse, AvailabilityZonesAndRegions] + sub_responses = [InstanceResponse, TagResponse, AmisResponse, AvailabilityZonesAndRegions, SecurityGroups] def dispatch(self, uri, body, headers): if body: diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 15351811b..10541d898 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -5,6 +5,9 @@ from moto.ec2.utils import resource_ids_from_querystring class SecurityGroups(object): + def __init__(self, querystring): + self.querystring = querystring + def authorize_security_group_egress(self): raise NotImplementedError('SecurityGroups.authorize_security_group_egress is not yet implemented') @@ -12,13 +15,28 @@ class SecurityGroups(object): raise NotImplementedError('SecurityGroups.authorize_security_group_ingress is not yet implemented') def create_security_group(self): - raise NotImplementedError('SecurityGroups.create_security_group is not yet implemented') + name = self.querystring.get('GroupName')[0] + description = self.querystring.get('GroupDescription')[0] + group = ec2_backend.create_security_group(name, description) + if not group: + # There was an exisitng group + return "There was an existing security group with name {}".format(name), dict(status=409) + template = Template(CREATE_SECURITY_GROUP_RESPONSE) + return template.render(group=group) def delete_security_group(self): - raise NotImplementedError('SecurityGroups.delete_security_group is not yet implemented') + name = self.querystring.get('GroupName')[0] + group = ec2_backend.delete_security_group(name) + + if not group: + # There was no such group + return "There was no security group with name {}".format(name), dict(status=404) + return DELETE_GROUP_RESPONSE def describe_security_groups(self): - raise NotImplementedError('SecurityGroups.describe_security_groups is not yet implemented') + groups = ec2_backend.describe_security_groups() + template = Template(DESCRIBE_SECURITY_GROUPS_RESPONSE) + return template.render(groups=groups) def revoke_security_group_egress(self): raise NotImplementedError('SecurityGroups.revoke_security_group_egress is not yet implemented') @@ -26,3 +44,43 @@ class SecurityGroups(object): def revoke_security_group_ingress(self): raise NotImplementedError('SecurityGroups.revoke_security_group_ingress is not yet implemented') + +CREATE_SECURITY_GROUP_RESPONSE = """ + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true + {{ group.id }} +""" + +DELETE_GROUP_RESPONSE = """ + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true +""" + +DESCRIBE_SECURITY_GROUPS_RESPONSE = """ + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + + {% for group in groups %} + + 111122223333 + {{ group.id }} + {{ group.name }} + {{ group.description }} + + + + tcp + 80 + 80 + + + + 0.0.0.0/0 + + + + + + + {% endfor %} + +""" \ No newline at end of file diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index c86bafa71..bbe808d4f 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -22,6 +22,10 @@ def random_ami_id(): return random_id(prefix='ami') +def random_security_group_id(): + return random_id(prefix='sg') + + def instance_ids_from_querystring(querystring_dict): instance_ids = [] for key, value in querystring_dict.iteritems(): diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 9bba776f9..95476892c 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -1,9 +1,41 @@ import boto +from boto.exception import EC2ResponseError from sure import expect from moto import mock_ec2 @mock_ec2 -def test_security_groups(): - pass +def test_create_and_describe_security_group(): + conn = boto.connect_ec2('the_key', 'the_secret') + security_group = conn.create_security_group('test security group', 'this is a test security group') + + security_group.name.should.equal('test security group') + security_group.description.should.equal('this is a test security group') + + # Trying to create another group with the same name should throw an error + conn.create_security_group.when.called_with('test security group', 'this is a test security group').should.throw(EC2ResponseError) + + all_groups = conn.get_all_security_groups() + all_groups.should.have.length_of(1) + all_groups[0].name.should.equal('test security group') + + +@mock_ec2 +def test_deleting_security_groups(): + conn = boto.connect_ec2('the_key', 'the_secret') + security_group1 = conn.create_security_group('test1', 'test1') + security_group2 = conn.create_security_group('test2', 'test2') + + conn.get_all_security_groups().should.have.length_of(2) + + # Deleting a group that doesn't exist should throw an error + conn.delete_security_group.when.called_with('foobar').should.throw(EC2ResponseError) + + # Delete by name + conn.delete_security_group('test2') + conn.get_all_security_groups().should.have.length_of(1) + + # Delete by group id + conn.delete_security_group(security_group1.id) + conn.get_all_security_groups().should.have.length_of(0)