Nodes now have a `created_at` attribute
* Base Node object has `created_at` which indicates the `datetime` the
node was launched/started/created.
* EC2, Digital Ocean, OpenStack fill this attribute.
* Nodes at drivers that do not (yet) support it have `NoneType` as date.
* Document changes.
closes #698
[GITHUB-698]
Signed-off-by: Allard Hoeve <allardhoeve@gmail.com>
diff --git a/CHANGES.rst b/CHANGES.rst
index a5d882e..3168cce 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -51,6 +51,11 @@
(GITHUB-697)
[Rick van de Loo]
+- Added `Node.create_at` which, on supported drivers, contains the datetime the
+ node was first started.
+ (GITHUB-698)
+ [Allard Hoeve]
+
Storage
~~~~~~~
diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py
index 2b9b18a..b64dd31 100644
--- a/libcloud/compute/base.py
+++ b/libcloud/compute/base.py
@@ -167,7 +167,7 @@
"""
def __init__(self, id, name, state, public_ips, private_ips,
- driver, size=None, image=None, extra=None):
+ driver, size=None, image=None, extra=None, created_at=None):
"""
:param id: Node ID.
:type id: ``str``
@@ -193,6 +193,9 @@
:param image: Image of this node. (optional)
:type size: :class:`.NodeImage`
+ :param created_at: The datetime this node was created (optional)
+ :type created_at: :class: `datetime.datetime`
+
:param extra: Optional provider specific attributes associated with
this node.
:type extra: ``dict``
@@ -205,6 +208,7 @@
self.private_ips = private_ips if private_ips else []
self.driver = driver
self.size = size
+ self.created_at = created_at
self.image = image
self.extra = extra or {}
UuidMixin.__init__(self)
diff --git a/libcloud/compute/drivers/digitalocean.py b/libcloud/compute/drivers/digitalocean.py
index d0b3d7b..a390352 100644
--- a/libcloud/compute/drivers/digitalocean.py
+++ b/libcloud/compute/drivers/digitalocean.py
@@ -18,6 +18,7 @@
import json
import warnings
+from libcloud.utils.iso8601 import parse_date
from libcloud.utils.py3 import httplib
from libcloud.common.digitalocean import DigitalOcean_v1_BaseDriver
@@ -551,6 +552,7 @@
else:
state = NodeState.UNKNOWN
+ created = parse_date(data['created_at'])
networks = data['networks']
private_ips = []
public_ips = []
@@ -568,7 +570,7 @@
node = Node(id=data['id'], name=data['name'], state=state,
public_ips=public_ips, private_ips=private_ips,
- driver=self, extra=extra)
+ created_at=created, driver=self, extra=extra)
return node
def _to_image(self, data):
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index 1df3efd..8367a14 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -5390,6 +5390,8 @@
except KeyError:
state = NodeState.UNKNOWN
+ created = parse_date(findtext(element=element, xpath='launchTime',
+ namespace=NAMESPACE))
instance_id = findtext(element=element, xpath='instanceId',
namespace=NAMESPACE)
public_ip = findtext(element=element, xpath='ipAddress',
@@ -5421,7 +5423,8 @@
return Node(id=instance_id, name=name, state=state,
public_ips=public_ips, private_ips=private_ips,
- driver=self.connection.driver, extra=extra)
+ driver=self.connection.driver, created_at=created,
+ extra=extra)
def _to_images(self, object):
return [self._to_image(el) for el in object.findall(
diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py
index 0fe5722..199a7af 100644
--- a/libcloud/compute/drivers/openstack.py
+++ b/libcloud/compute/drivers/openstack.py
@@ -2096,6 +2096,7 @@
image_id = image.get('id', None) if image else None
config_drive = api_node.get("config_drive", False)
volumes_attached = api_node.get('os-extended-volumes:volumes_attached')
+ created = parse_date(api_node["created"])
return Node(
id=api_node['id'],
@@ -2104,6 +2105,7 @@
NodeState.UNKNOWN),
public_ips=public_ips,
private_ips=private_ips,
+ created_at=created,
driver=self,
extra=dict(
hostId=api_node['hostId'],
diff --git a/libcloud/test/compute/test_digitalocean_v1.py b/libcloud/test/compute/test_digitalocean_v1.py
index 5719500..a09b1a8 100644
--- a/libcloud/test/compute/test_digitalocean_v1.py
+++ b/libcloud/test/compute/test_digitalocean_v1.py
@@ -88,6 +88,10 @@
self.assertEqual(nodes[0].extra['image_id'], 1601)
self.assertEqual(nodes[0].extra['size_id'], 66)
+ def test_list_nodes_does_not_support_created_datetime(self):
+ nodes = self.driver.list_nodes()
+ self.assertIsNone(nodes[0].created_at)
+
def test_create_node_invalid_size(self):
image = NodeImage(id='invalid', name=None, driver=self.driver)
size = self.driver.list_sizes()[0]
diff --git a/libcloud/test/compute/test_digitalocean_v2.py b/libcloud/test/compute/test_digitalocean_v2.py
index a4a1b01..5f14b7c 100644
--- a/libcloud/test/compute/test_digitalocean_v2.py
+++ b/libcloud/test/compute/test_digitalocean_v2.py
@@ -15,6 +15,9 @@
import sys
import unittest
+from datetime import datetime
+from libcloud.utils.iso8601 import UTC
+
try:
import simplejson as json
except ImportError:
@@ -87,6 +90,10 @@
self.assertEqual(nodes[0].extra['image']['id'], 6918990)
self.assertEqual(nodes[0].extra['size_slug'], '512mb')
+ def test_list_nodes_fills_created_datetime(self):
+ nodes = self.driver.list_nodes()
+ self.assertEqual(nodes[0].created_at, datetime(2014, 11, 14, 16, 29, 21, tzinfo=UTC))
+
def test_create_node_invalid_size(self):
image = NodeImage(id='invalid', name=None, driver=self.driver)
size = self.driver.list_sizes()[0]
diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py
index 243425c..4f1bbe1 100644
--- a/libcloud/test/compute/test_ec2.py
+++ b/libcloud/test/compute/test_ec2.py
@@ -217,8 +217,10 @@
self.assertEqual(node.id, 'i-4382922a')
self.assertEqual(node.name, node.id)
self.assertEqual(len(node.public_ips), 2)
- self.assertEqual(node.extra['launch_time'],
- '2013-12-02T11:58:11.000Z')
+
+ self.assertEqual(node.extra['launch_time'], '2013-12-02T11:58:11.000Z')
+ self.assertEqual(node.created_at, datetime(2013, 12, 2, 11, 58, 11, tzinfo=UTC))
+
self.assertTrue('instance_type' in node.extra)
self.assertEqual(node.extra['availability'], 'us-east-1d')
self.assertEqual(node.extra['key_name'], 'fauxkey')
@@ -243,12 +245,14 @@
self.assertEqual(ret_node2.extra['subnet_id'], 'subnet-5fd9d412')
self.assertEqual(ret_node2.extra['vpc_id'], 'vpc-61dcd30e')
self.assertEqual(ret_node2.extra['tags']['Group'], 'VPC Test')
- self.assertEqual(ret_node1.extra['launch_time'],
- '2013-12-02T11:58:11.000Z')
- self.assertTrue('instance_type' in ret_node1.extra)
- self.assertEqual(ret_node2.extra['launch_time'],
- '2013-12-02T15:58:29.000Z')
- self.assertTrue('instance_type' in ret_node2.extra)
+
+ self.assertEqual(ret_node1.extra['launch_time'], '2013-12-02T11:58:11.000Z')
+ self.assertEqual(ret_node1.created_at, datetime(2013, 12, 2, 11, 58, 11, tzinfo=UTC))
+ self.assertEqual(ret_node2.extra['launch_time'], '2013-12-02T15:58:29.000Z')
+ self.assertEqual(ret_node2.created_at, datetime(2013, 12, 2, 15, 58, 29, tzinfo=UTC))
+
+ self.assertIn('instance_type', ret_node1.extra)
+ self.assertIn('instance_type', ret_node2.extra)
def test_ex_list_reserved_nodes(self):
node = self.driver.ex_list_reserved_nodes()[0]
diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py
index ef8d341..ee2469d 100644
--- a/libcloud/test/compute/test_openstack.py
+++ b/libcloud/test/compute/test_openstack.py
@@ -775,6 +775,9 @@
self.assertTrue(
'fec0:4801:7808:52:16:3eff:fe60:187d' in node.private_ips)
+ # test creation date
+ self.assertEqual(node.created_at, datetime.datetime(2011, 10, 11, 0, 51, 39, tzinfo=UTC))
+
self.assertEqual(node.extra.get('flavorId'), '2')
self.assertEqual(node.extra.get('imageId'), '7')
self.assertEqual(node.extra.get('metadata'), {})