Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement floating ips digitalocean #1177

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions libcloud/compute/drivers/digitalocean.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,62 @@ def delete_volume_snapshot(self, snapshot):
method='DELETE')
return res.status == httplib.NO_CONTENT

def ex_create_floating_ip(self, location):
"""
Create new floating IP reserved to a region.

The newly created floating IP will not be associated to a Droplet.

See https://developers.digitalocean.com/documentation/v2/#floating-ips

:param location: Which data center to create the floating IP in.
:type location: :class:`.NodeLocation`

:rtype: :class:`DigitalOcean_v2_FloatingIpAddress`
"""
attr = {'region': location.id}
resp = self.connection.request('/v2/floating_ips',
data=json.dumps(attr), method='POST')
return self._to_floating_ip(resp.object['floating_ip'])

def ex_delete_floating_ip(self, ip):
"""
Delete specified floating IP

:param ip: floating IP to remove
:type ip: :class:`DigitalOcean_v2_FloatingIpAddress`

:rtype: ``bool``
"""
resp = self.connection.request('/v2/floating_ips/{}'.format(ip.id),
method='DELETE')
return resp.status == httplib.NO_CONTENT

def ex_list_floating_ips(self):
"""
List floating IPs

:rtype: ``list`` of :class:`DigitalOcean_v2_FloatingIpAddress`
"""
return self._to_floating_ips(
self._paginated_request('/v2/floating_ips', 'floating_ips')
)

def ex_get_floating_ip(self, ip):
"""
Get specified floating IP

:param ip: floating IP to get
:type ip: ``str``

:rtype: :class:`DigitalOcean_v2_FloatingIpAddress`
"""
floating_ips = self.ex_list_floating_ips()
matching_ips = [x for x in floating_ips if x.ip_address == ip]
if not matching_ips:
raise ValueError('Floating ip %s not found' % ip)
return matching_ips[0]

def _to_node(self, data):
extra_keys = ['memory', 'vcpus', 'disk', 'region', 'image',
'size_slug', 'locked', 'created_at', 'networks',
Expand Down Expand Up @@ -573,3 +629,44 @@ def _to_volume_snapshot(self, data):
return VolumeSnapshot(id=data['id'], name=data['name'],
size=data['size_gigabytes'],
driver=self, extra=extra)

def _to_floating_ips(self, obj):
return [self._to_floating_ip(ip) for ip in obj]

def _to_floating_ip(self, obj):
return DigitalOcean_v2_FloatingIpAddress(
# There is no ID, but the IP is unique so we can use that
id=obj['ip'],
ip_address=obj['ip'],
node_id=obj['droplet']['id'] if obj['droplet'] else None,
extra={
'region': obj['region'],
},
driver=self
)


class DigitalOcean_v2_FloatingIpAddress(object):
"""
Floating IP info.
"""

def __init__(self, id, ip_address, node_id=None, extra=None, driver=None):
self.id = str(id)
self.ip_address = ip_address
self.extra = extra
self.node_id = node_id
self.driver = driver

def delete(self):
"""
Delete this floating IP

:rtype: ``bool``
"""
return self.driver.ex_delete_floating_ip(self)

def __repr__(self):
return ('<DigitalOcean_v2_FloatingIpAddress: id=%s, ip_addr=%s,'
' driver=%s>'
% (self.id, self.ip_address, self.driver))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"floating_ip":{"ip":"167.138.123.111","droplet":null,"region":{"name":"New York 1","slug":"nyc1","sizes":["64gb","512mb","1gb","2gb","4gb","8gb","16gb","32gb","48gb","c-16","s-1vcpu-3gb","c-2","c-4","c-8","m-1vcpu-8gb","m-16gb","m-32gb","m-64gb","m-128gb","m-224gb","s-1vcpu-1gb","s-3vcpu-1gb","s-1vcpu-2gb","s-2vcpu-2gb","s-2vcpu-4gb","s-4vcpu-8gb","s-6vcpu-16gb","s-8vcpu-32gb","s-12vcpu-48gb","s-16vcpu-64gb","s-20vcpu-96gb","s-24vcpu-128gb"],"features":["private_networking","backups","ipv6","metadata","install_agent","storage"],"available":true},"locked":false},"links":{}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"floating_ips":[{"ip":"133.166.122.204","droplet":{"status":"active","kernel": null,"volume_ids": [],"locked": false,"name":"vdloo-magweb","backup_ids": [],"created_at":"2018-03-01T10:15:06Z","snapshot_ids": [],"size_slug":"2gb","id": 84155775,"next_backup_window": null,"vcpus": 2,"features": [],"image": {"regions": ["ams3"],"min_disk_size": 80,"public": false,"name":"hypernode","distribution":"Ubuntu","created_at":"2018-05-23T14:16:34Z","size_gigabytes": 1.02,"type":"snapshot","slug": null,"id": 12367123},"memory": 4096,"region": {"available": true,"slug":"ams3","features": ["private_networking","backups","ipv6","metadata","install_agent","storage"],"name":"Amsterdam 3","sizes": ["64gb","512mb","1gb","2gb","4gb","8gb","16gb","32gb","36gb","c-16","s-1vcpu-3gb","c-2","c-4","c-8","m-1vcpu-8gb","m-16gb","m-32gb","m-64gb","m-128gb","m-224gb","s-1vcpu-1gb","s-3vcpu-1gb","s-1vcpu-2gb","s-2vcpu-2gb","s-2vcpu-4gb","s-4vcpu-8gb","s-6vcpu-16gb","s-8vcpu-32gb","s-12vcpu-36gb","s-16vcpu-64gb","s-20vcpu-96gb","s-24vcpu-128gb"]},"disk": 80,"networks": {"v4": [{"ip_address":"145.65.193.36","netmask":"255.255.280.0","type":"public","gateway":"145.65.192.1"}],"v6": []},"tags": [],"size": {"price_monthly": 20.0,"available": true,"vcpus": 2,"regions": ["ams2","ams3","blr1","fra1","lon1","nyc1","nyc2","nyc3","sfo1","sfo2","sgp1","tor1"],"memory": 4096,"transfer": 3.0,"disk": 80,"price_hourly": 0.02976,"slug":"2gb"}},"region":{"name":"Amsterdam 3","slug":"ams3","sizes":["64gb","512mb","1gb","2gb","4gb","8gb","16gb","32gb","36gb","c-16","s-1vcpu-3gb","c-2","c-4","c-8","m-1vcpu-8gb","m-16gb","m-32gb","m-64gb","m-128gb","m-224gb","s-1vcpu-1gb","s-3vcpu-1gb","s-1vcpu-2gb","s-2vcpu-2gb","s-2vcpu-4gb","s-4vcpu-8gb","s-6vcpu-16gb","s-8vcpu-32gb","s-12vcpu-36gb","s-16vcpu-64gb","s-20vcpu-96gb","s-24vcpu-128gb"],"features":["private_networking","backups","ipv6","metadata","install_agent","storage"],"available":true},"locked":false},{"ip":"154.138.103.175","droplet":null,"region":{"name":"Amsterdam 3","slug":"ams3","sizes":["64gb","512mb","1gb","2gb","4gb","8gb","16gb","32gb","36gb","c-16","s-1vcpu-3gb","c-2","c-4","c-8","m-1vcpu-8gb","m-16gb","m-32gb","m-64gb","m-128gb","m-224gb","s-1vcpu-1gb","s-3vcpu-1gb","s-1vcpu-2gb","s-2vcpu-2gb","s-2vcpu-4gb","s-4vcpu-8gb","s-6vcpu-16gb","s-8vcpu-32gb","s-12vcpu-36gb","s-16vcpu-64gb","s-20vcpu-96gb","s-24vcpu-128gb"],"features":["private_networking","backups","ipv6","metadata","install_agent","storage"],"available":true},"locked":false}],"links":{},"meta":{"total":2}}

66 changes: 66 additions & 0 deletions libcloud/test/compute/test_digitalocean_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,55 @@ def test_delete_volume_snapshot(self):
result = self.driver.delete_volume_snapshot(snapshot)
self.assertTrue(result)

def test_ex_create_floating_ip(self):
nyc1 = [r for r in self.driver.list_locations() if r.id == 'nyc1'][0]
floating_ip = self.driver.ex_create_floating_ip(nyc1)

# Note that this is the ID. There is no real ID for a floating IP at
# DigitalOcean, but the IP is unique so we can use that instead.
self.assertEqual(floating_ip.id, '167.138.123.111')
self.assertEqual(floating_ip.ip_address, '167.138.123.111')
self.assertEqual(floating_ip.extra['region']['slug'], 'nyc1')
# The newly created floating IP reserved to a region is not
# associated with any droplet. See the DigitalOcean API docs
# how to create a floating IP that is associated with an instance
# from the start. This API call creates an unattached IP.
self.assertIsNone(floating_ip.node_id)

def test_ex_delete_floating_ip(self):
nyc1 = [r for r in self.driver.list_locations() if r.id == 'nyc1'][0]
floating_ip = self.driver.ex_create_floating_ip(nyc1)
ret = self.driver.ex_delete_floating_ip(floating_ip)

# The API returns 204 NO CONTENT if all is well.
self.assertTrue(ret)

def test_floating_ip_can_be_deleted_by_calling_delete_on_floating_ip_object(self):
nyc1 = [r for r in self.driver.list_locations() if r.id == 'nyc1'][0]
floating_ip = self.driver.ex_create_floating_ip(nyc1)
ret = floating_ip.delete()

self.assertTrue(ret)

def test_list_floating_ips(self):
floating_ips = self.driver.ex_list_floating_ips()

self.assertEqual(len(floating_ips), 2, 'Wrong floating IPs count')

floating_ip = floating_ips[0]
self.assertEqual(floating_ip.id, '133.166.122.204')
self.assertEqual(floating_ip.ip_address, '133.166.122.204')
self.assertEqual(floating_ip.extra['region']['slug'], 'ams3')
self.assertEqual(84155775, floating_ip.node_id)

def test_get_floating_ip(self):
floating_ip = self.driver.ex_get_floating_ip('133.166.122.204')

self.assertEqual(floating_ip.id, '133.166.122.204')
self.assertEqual(floating_ip.ip_address, '133.166.122.204')
self.assertEqual(floating_ip.extra['region']['slug'], 'ams3')
self.assertEqual(84155775, floating_ip.node_id)


class DigitalOceanMockHttp(MockHttp):
fixtures = ComputeFileFixtures('digitalocean_v2')
Expand Down Expand Up @@ -452,6 +501,23 @@ def _v2_snapshots_c0def940_9324_11e6_9a56_000f533176b1_DELETE(
return (httplib.NO_CONTENT, None, {},
httplib.responses[httplib.NO_CONTENT])

def _v2_floating_ips(self, method, url, body, headers):
if method == 'POST':
body = self.fixtures.load('create_floating_ip.json')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
elif method == 'GET':
body = self.fixtures.load('list_floating_ips.json')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
else:
raise NotImplementedError()

def _v2_floating_ips_167_138_123_111(self, method, url, body, headers):
if method == 'DELETE':
body = ''
return (httplib.NO_CONTENT, body, {}, httplib.responses[httplib.NO_CONTENT])
else:
raise NotImplementedError()


if __name__ == '__main__':
sys.exit(unittest.main())