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 action
from openstack.cluster.v1 import cluster from openstack.cluster.v1 import cluster
from openstack.cluster.v1 import node
from openstack.cluster.v1 import policy from openstack.cluster.v1 import policy
from openstack import proxy from openstack import proxy
@ -83,6 +84,7 @@ class Proxy(proxy.BaseProxy):
whether a cluster should be included in the list result. whether a cluster should be included in the list result.
* sort_keys: A list of key names for sorting the resulted list. * sort_keys: A list of key names for sorting the resulted list.
* sort_dir: Direction for sorting, and its valid values are 'asc' * sort_dir: Direction for sorting, and its valid values are 'asc'
and 'desc'.
* limit: Requests a specified size of returned items from the * limit: Requests a specified size of returned items from the
query. Returns a number of items up to the specified limit query. Returns a number of items up to the specified limit
value. value.
@ -108,6 +110,92 @@ class Proxy(proxy.BaseProxy):
""" """
return self._update(cluster.Cluster, value, **attrs) 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): def create_policy(self, **attrs):
"""Create a new policy from attributes. """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 _proxy
from openstack.cluster.v1 import action from openstack.cluster.v1 import action
from openstack.cluster.v1 import cluster from openstack.cluster.v1 import cluster
from openstack.cluster.v1 import node
from openstack.cluster.v1 import policy from openstack.cluster.v1 import policy
from openstack.tests.unit import test_proxy_base from openstack.tests.unit import test_proxy_base
@ -47,6 +48,31 @@ class TestClusterProxy(test_proxy_base.TestProxyBase):
def test_cluster_update(self): def test_cluster_update(self):
self.verify_update(self.proxy.update_cluster, cluster.Cluster) 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): def test_policy_create(self):
self.verify_create(self.proxy.create_policy, policy.Policy) self.verify_create(self.proxy.create_policy, policy.Policy)