Merge "Add node resource for cluster service"
This commit is contained in:
commit
7b0ba7bfbb
@ -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.
|
||||
|
||||
|
106
openstack/cluster/v1/node.py
Normal file
106
openstack/cluster/v1/node.py
Normal 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)
|
108
openstack/tests/unit/cluster/v1/test_node.py
Normal file
108
openstack/tests/unit/cluster/v1/test_node.py
Normal 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)
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user