Merge "Add node resource for cluster service"

This commit is contained in:
Jenkins 2015-10-22 08:31:49 +00:00 committed by Gerrit Code Review
commit 7b0ba7bfbb
4 changed files with 328 additions and 0 deletions

View File

@ -12,6 +12,7 @@
from openstack.cluster.v1 import action
from openstack.cluster.v1 import cluster
from openstack.cluster.v1 import node
from openstack.cluster.v1 import policy
from openstack import proxy
@ -83,6 +84,7 @@ class Proxy(proxy.BaseProxy):
whether a cluster should be included in the list result.
* sort_keys: A list of key names for sorting the resulted list.
* sort_dir: Direction for sorting, and its valid values are 'asc'
and 'desc'.
* limit: Requests a specified size of returned items from the
query. Returns a number of items up to the specified limit
value.
@ -108,6 +110,92 @@ class Proxy(proxy.BaseProxy):
"""
return self._update(cluster.Cluster, value, **attrs)
def create_node(self, **attrs):
"""Create a new node from attributes.
:param dict attrs: Keyword arguments that will be used to create a
:class:`~openstack.cluster.v1.node.Node`, it is comprised
of the properties on the Node class.
:returns: The results of node creation.
:rtype: :class:`~openstack.cluster.v1.node.Node`.
"""
return self._create(node.Node, **attrs)
def delete_node(self, value, ignore_missing=True):
"""Delete a node.
:param value: The value can be either the name or ID of a node or a
:class:`~openstack.cluster.v1.node.Node` instance.
:param bool ignore_missing: When set to ``False``, an exception
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
the node could not be found. When set to ``True``, no exception
will be raised when attempting to delete a non-existent node.
:returns: ``None``
"""
self._delete(node.Node, value, ignore_missing=ignore_missing)
def find_node(self, value, ignore_missing=True):
"""Find a single node.
:param value: The name or ID of a node.
:returns: One :class:`~openstack.cluster.v1.node.Node` object or None.
"""
return node.Node.find(self.session, value,
ignore_missing=ignore_missing)
def get_node(self, value):
"""Get a single node.
:param value: The value can be the name or ID of a node or a
:class:`~openstack.cluster.v1.node.Node` instance.
:returns: One :class:`~openstack.cluster.v1.node.Node`
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
node matching the name or ID could be found.
"""
return self._get(node.Node, value)
def nodes(self, **query):
"""Retrieve a generator of nodes.
:param kwargs \*\*query: Optional query parameters to be sent to
restrict the nodes to be returned. Available parameters include:
* cluster_id: A string including the name or ID of a cluster to
which the resulted node(s) is a member.
* show_deleted: A boolean value indicating whether soft-deleted
nodes should be returned as well.
* filters: A list of key-value pairs for server to determine
whether a node should be included in the list result.
* sort_keys: A list of key names for sorting the resulted list.
* sort_dir: Direction for sorting, and its valid values are 'asc'
and 'desc'.
* limit: Requests at most the specified number of items be
returned from the query.
* marker: Specifies the ID of the last-seen node. Use the limit
parameter to make an initial limited request and use the ID of
the last-seen node from the response as the marker parameter
value in a subsequent limited request.
:returns: A generator of node instances.
"""
return self._list(node.Node, paginated=True, **query)
def update_node(self, value, **attrs):
"""Update a node.
:param value: Either the name or the ID of the node, or an instance
of :class:`~openstack.cluster.v1.node.Node`.
:param attrs: The attributes to update on the node represented by
the ``value`` parameter.
:returns: The updated node.
:rtype: :class:`~openstack.cluster.v1.node.Node`
"""
return self._update(node.Node, value, **attrs)
def create_policy(self, **attrs):
"""Create a new policy from attributes.

View File

@ -0,0 +1,106 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack.cluster import cluster_service
from openstack import resource
from openstack import utils
class Node(resource.Resource):
resource_key = 'node'
resources_key = 'nodes'
base_path = '/nodes'
service = cluster_service.ClusterService()
# capabilities
allow_create = True
allow_retrieve = True
allow_update = True
allow_delete = True
allow_list = True
# Properties
#: The name of the node.
name = resource.prop('name')
#: The ID of the physical object that backs the node.
physical_id = resource.prop('physical_id')
#: The ID of the cluster in which this node is a member.
#: A node is an orphan node if this field is empty.
cluster_id = resource.prop('cluster_id')
#: The ID of the profile used by this node.
profile_id = resource.prop('profile_id')
#: The ID of the project this node belongs to.
project = resource.prop('project')
#: The name of the profile used by this node.
profile_name = resource.prop('profile_name')
#: An integer that is unique inside the owning cluster.
#: A value of -1 means this node is an orphan node.
index = resource.prop('index', type=int)
#: A string indicating the role the node plays in a cluster.
role = resource.prop('role')
#: The timestamp of the node object's initialization.
init_time = resource.prop('init_time')
#: The timestamp of the node's creation, i.e. the physical object
#: represented by this node is also created.
created_time = resource.prop('created_time')
#: The timestamp the node was last updated.
updated_time = resource.prop('updated_time')
#: The timestamp the node was deleted. This is only used for node
#: which has been soft deleted.
deleted_time = resource.prop('deleted_time')
#: A string indicating the node's status.
status = resource.prop('status')
#: A string describing why the node entered its current status.
status_reason = resource.prop('status_reason')
#: A map containing key-value pairs attached to the node.
metadata = resource.prop('tags', type=dict)
#: A map containing some runtime data for this node.
data = resource.prop('data', type=dict)
#: A map containing the details of the physical object this node
#: represents
details = resource.prop('details', type=dict)
def _action(self, session, body):
"""Procedure the invoke an action API.
:param session: A session object used for sending request.
:param body: The body of action to be sent.
"""
url = utils.urljoin(self.base_path, self.id, 'action')
resp = session.put(url, service=self.service, json=body).body
return resp
def join(self, session, cluster_id):
"""An action procedure for the node to join a cluster.
:param session: A session object used for sending request.
:param cluster_id: The ID, name or short ID of a cluster the
node is about to join.
:returns: A dictionary containing the action ID.
"""
body = {
'join': {
'cluster_id': cluster_id,
}
}
return self._action(session, body)
def leave(self, session):
"""An action procedure for the node to leave its current cluster.
:param session: A session object used for sending request.
:returns: A dictionary containing the action ID.
"""
body = {
'leave': {}
}
return self._action(session, body)

View File

@ -0,0 +1,108 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import testtools
from openstack.cluster.v1 import node
FAKE_ID = '123d0955-0099-aabb-b8fa-6a44655ceeff'
FAKE_NAME = 'test_node'
FAKE = {
'id': FAKE_ID,
'cluster_id': 'clusterA',
'metadata': {},
'name': FAKE_NAME,
'profile_id': 'myserver',
'index': 1,
'role': 'master',
}
FAKE_CREATE_RESP = {
'node': {
'id': FAKE_ID,
'name': FAKE_NAME,
'cluster_id': '99001122-aabb-ccdd-ffff-efdcab124567',
'action': '1122aabb-eeff-7755-2222-00991234dcba',
'created_time': None,
'deleted_time': None,
'updated_time': None,
'data': {},
'role': 'master',
'index': 1,
'init_time': None,
'metadata': {},
'profile_id': '560a8f9d-7596-4a32-85e8-03645fa7be13',
'profile_name': 'myserver',
'project': '333acb15a43242f4a609a27cb097a8f2',
'status': 'INIT',
'status_reason': 'Initializing',
}
}
class TestNode(testtools.TestCase):
def setUp(self):
super(TestNode, self).setUp()
def test_basic(self):
sot = node.Node()
self.assertEqual('node', sot.resource_key)
self.assertEqual('nodes', sot.resources_key)
self.assertEqual('/nodes', sot.base_path)
self.assertEqual('clustering', sot.service.service_type)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_retrieve)
self.assertTrue(sot.allow_update)
self.assertTrue(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_instantiate(self):
sot = node.Node(FAKE)
self.assertEqual(FAKE['id'], sot.id)
self.assertEqual(FAKE['name'], sot.name)
self.assertEqual(FAKE['profile_id'], sot.profile_id)
self.assertEqual(FAKE['cluster_id'], sot.cluster_id)
self.assertEqual(FAKE['name'], sot.name)
self.assertEqual(FAKE['index'], sot.index)
self.assertEqual(FAKE['role'], sot.role)
self.assertEqual(FAKE['metadata'], sot.metadata)
def test_join(self):
sot = node.Node(FAKE)
sot['id'] = 'IDENTIFIER'
resp = mock.Mock()
resp.body = {'action': '1234-5678-abcd'}
sess = mock.Mock()
sess.put = mock.MagicMock(return_value=resp)
self.assertEqual(resp.body, sot.join(sess, 'cluster-b'))
url = 'nodes/%s/action' % sot.id
body = {'join': {'cluster_id': 'cluster-b'}}
sess.put.assert_called_once_with(url, service=sot.service, json=body)
def test_leave(self):
sot = node.Node(FAKE)
sot['id'] = 'IDENTIFIER'
resp = mock.Mock()
resp.body = {'action': '2345-6789-bbbb'}
sess = mock.Mock()
sess.put = mock.MagicMock(return_value=resp)
self.assertEqual(resp.body, sot.leave(sess))
url = 'nodes/%s/action' % sot.id
body = {'leave': {}}
sess.put.assert_called_once_with(url, service=sot.service, json=body)

View File

@ -13,6 +13,7 @@
from openstack.cluster.v1 import _proxy
from openstack.cluster.v1 import action
from openstack.cluster.v1 import cluster
from openstack.cluster.v1 import node
from openstack.cluster.v1 import policy
from openstack.tests.unit import test_proxy_base
@ -47,6 +48,31 @@ class TestClusterProxy(test_proxy_base.TestProxyBase):
def test_cluster_update(self):
self.verify_update(self.proxy.update_cluster, cluster.Cluster)
def test_node_create(self):
self.verify_create(self.proxy.create_node, node.Node)
def test_node_delete(self):
self.verify_delete(self.proxy.delete_node, node.Node, False)
def test_node_delete_ignore(self):
self.verify_delete(self.proxy.delete_node, node.Node, True)
def test_node_find(self):
self.verify_find('openstack.cluster.v1.node.Node.find',
self.proxy.find_node)
def test_node_get(self):
self.verify_get(self.proxy.get_node, node.Node)
def test_nodes(self):
self.verify_list(self.proxy.nodes, node.Node,
paginated=True,
method_kwargs={'limit': 2},
expected_kwargs={'limit': 2})
def test_node_update(self):
self.verify_update(self.proxy.update_node, node.Node)
def test_policy_create(self):
self.verify_create(self.proxy.create_policy, policy.Policy)