Merge "Implement Node commands"
This commit is contained in:
101
ironicclient/tests/v1/test_node.py
Normal file
101
ironicclient/tests/v1/test_node.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 copy
|
||||
import testtools
|
||||
|
||||
from ironicclient.tests import utils
|
||||
import ironicclient.v1.node
|
||||
|
||||
NODE = {'id': 123,
|
||||
'uuid': '66666666-7777-8888-9999-000000000000',
|
||||
'chassis_id': 42,
|
||||
'driver': 'fake',
|
||||
'driver_info': {'user': 'foo', 'password': 'bar'},
|
||||
'properties': {'num_cpu': 4},
|
||||
'extra': {}}
|
||||
|
||||
CREATE_NODE = copy.deepcopy(NODE)
|
||||
del CREATE_NODE['id']
|
||||
del CREATE_NODE['uuid']
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v1/nodes':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"nodes": [NODE]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
CREATE_NODE,
|
||||
),
|
||||
},
|
||||
'/v1/nodes/%s' % NODE['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
NODE,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class NodeManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NodeManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fixtures)
|
||||
self.mgr = ironicclient.v1.node.NodeManager(self.api)
|
||||
|
||||
def test_node_list(self):
|
||||
node = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/nodes', {}, None),
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertEqual(len(node), 1)
|
||||
|
||||
def test_node_show(self):
|
||||
node = self.mgr.get(NODE['uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/nodes/%s' % NODE['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertEqual(node.uuid, NODE['uuid'])
|
||||
|
||||
def test_create(self):
|
||||
node = self.mgr.create(**CREATE_NODE)
|
||||
expect = [
|
||||
('POST', '/v1/nodes', {}, CREATE_NODE),
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertTrue(node)
|
||||
|
||||
def test_delete(self):
|
||||
node = self.mgr.delete(node_id=NODE['uuid'])
|
||||
expect = [
|
||||
('DELETE', '/v1/nodes/%s' % NODE['uuid'], {}, None),
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertTrue(node is None)
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
from ironicclient.common import http
|
||||
from ironicclient.v1 import chassis
|
||||
from ironicclient.v1 import node
|
||||
from ironicclient.v1 import port
|
||||
|
||||
|
||||
@@ -32,4 +33,5 @@ class Client(http.HTTPClient):
|
||||
"""Initialize a new client for the Ironic v1 API."""
|
||||
super(Client, self).__init__(*args, **kwargs)
|
||||
self.chassis = chassis.ChassisManager(self)
|
||||
self.node = node.NodeManager(self)
|
||||
self.port = port.PortManager(self)
|
||||
|
||||
56
ironicclient/v1/node.py
Normal file
56
ironicclient/v1/node.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 ironicclient.common import base
|
||||
from ironicclient import exc
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['chassis_id', 'driver', 'driver_info', 'extra',
|
||||
'node_id', 'properties']
|
||||
|
||||
|
||||
class Node(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Node %s>" % self._info
|
||||
|
||||
|
||||
class NodeManager(base.Manager):
|
||||
resource_class = Node
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None):
|
||||
return '/v1/nodes/%s' % id if id else '/v1/nodes'
|
||||
|
||||
def list(self):
|
||||
return self._list(self._path(), "nodes")
|
||||
|
||||
def get(self, node_id):
|
||||
try:
|
||||
return self._list(self._path(node_id))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def create(self, **kwargs):
|
||||
new = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in CREATION_ATTRIBUTES:
|
||||
new[key] = value
|
||||
else:
|
||||
raise exc.InvalidAttribute()
|
||||
return self._create(self._path(), new)
|
||||
|
||||
def delete(self, node_id):
|
||||
return self._delete(self._path(node_id))
|
||||
@@ -65,6 +65,71 @@ def do_chassis_delete(self, args):
|
||||
raise exc.CommandError('Chassis not found: %s' % args.chassis)
|
||||
|
||||
|
||||
@utils.arg('node', metavar='<node>', help="ID of node")
|
||||
def do_node_show(self, args):
|
||||
"""Show a node."""
|
||||
try:
|
||||
node = self.node.get(args.node)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('Node not found: %s' % args.node)
|
||||
else:
|
||||
fields = ['uuid', 'instance_uuid', 'power_state', 'target_power_state',
|
||||
'provision_state', 'target_provision_state', 'driver',
|
||||
'driver_info', 'properties', 'extra',
|
||||
'created_at', 'updated_at', 'reservation']
|
||||
data = dict([(f, getattr(node, f, '')) for f in fields])
|
||||
utils.print_dict(data, wrap=72)
|
||||
|
||||
|
||||
def do_node_list(self, args):
|
||||
"""List nodes."""
|
||||
nodes = self.node.list()
|
||||
field_labels = ['UUID', 'Instance UUID',
|
||||
'Power State', 'Provisioning State']
|
||||
fields = ['uuid', 'instance_uuid', 'power_state', 'provision_state']
|
||||
utils.print_list(nodes, fields, field_labels, sortby=1)
|
||||
|
||||
|
||||
@utils.arg('--driver',
|
||||
metavar='<DRIVER>',
|
||||
help='Driver used to control the node. [REQUIRED]')
|
||||
@utils.arg('--driver_info',
|
||||
metavar='<key=value>',
|
||||
help='Key/value pairs used by the driver. '
|
||||
'Can be specified multiple times.')
|
||||
@utils.arg('--properties',
|
||||
metavar='<key=value>',
|
||||
help='Key/value pairs describing the physical characteristics '
|
||||
'of the node. This is exported to Nova and used by the '
|
||||
'scheduler. Can be specified multiple times.')
|
||||
@utils.arg('--extra',
|
||||
metavar='<key=value>',
|
||||
help="Record arbitrary key/value metadata. "
|
||||
"Can be specified multiple times.")
|
||||
def do_node_create(self, args):
|
||||
"""Create a new node."""
|
||||
field_list = ['chassis_id', 'driver', 'driver_info', 'properties', 'extra']
|
||||
fields = dict((k, v) for (k, v) in vars(args).items()
|
||||
if k in field_list and not (v is None))
|
||||
fields = utils.args_array_to_dict(fields, 'driver_info')
|
||||
fields = utils.args_array_to_dict(fields, 'extra')
|
||||
fields = utils.args_array_to_dict(fields, 'properties')
|
||||
node = self.node.create(**fields)
|
||||
|
||||
field_list.append('uuid')
|
||||
data = dict([(f, getattr(node, f, '')) for f in field_list])
|
||||
utils.print_dict(data, wrap=72)
|
||||
|
||||
|
||||
@utils.arg('node', metavar='<node>', help="ID of node")
|
||||
def do_node_delete(self, args):
|
||||
"""Delete a node."""
|
||||
try:
|
||||
self.node.delete(args.node)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('Node not found: %s' % args.node)
|
||||
|
||||
|
||||
@utils.arg('port', metavar='<port>', help="ID of port")
|
||||
def do_port_show(self, args):
|
||||
"""Show a port."""
|
||||
|
||||
Reference in New Issue
Block a user