blueprint host-aggregates: client bindings

These are the client bindings for the new aggregate api added into nova in the following change:
https://review.openstack.org/#change,3109

Change-Id: I97e0223aa18d01450f82848c5be9bce78b83ef39
This commit is contained in:
John Garbutt 2012-01-13 13:36:01 +00:00 committed by Armando Migliaccio
parent 36be4bf575
commit 0213eb4c0a
9 changed files with 411 additions and 17 deletions

View File

@ -24,6 +24,7 @@ Jason Kölker <jason@koelker.net>
Jason Straw <jason.straw@rackspace.com>
Jesse Andrews <anotherjesse@gmail.com>
Johannes Erdfelt <johannes.erdfelt@rackspace.com>
John Garbutt <john.garbutt@citrix.com>
Josh Kearney <josh@jk0.org>
Kevin L. Mitchell <kevin.mitchell@rackspace.com>
Kiall Mac Innes <kiall@managedit.ie>

View File

@ -77,6 +77,18 @@ You'll find complete documentation on the shell by running
<subcommand>
add-fixed-ip Add a new fixed IP address to a servers network.
add-floating-ip Add a floating IP address to a server.
aggregate-add-host Add the host to the specified aggregate
aggregate-create Create a new aggregate with the specified details
aggregate-delete Delete the aggregate by its id
aggregate-details Show details of the specified aggregate
aggregate-list Print a list of all aggregates
aggregate-remove-host
Remove the specified host from the specfied
aggregate
aggregate-set-metadata
Update the metadata associated with the aggregate
aggregate-update Update the aggregate's name and optionally
availablity zone
backup Backup a server.
backup-schedule Show or edit the backup schedule for a server.
backup-schedule-delete

View File

@ -141,6 +141,7 @@ class Manager(utils.HookableMixin):
def _update(self, url, body, **kwargs):
self.run_hooks('modify_body_for_update', body, **kwargs)
resp, body = self.api.client.put(url, body=body)
return body
class ManagerWithFind(Manager):

View File

@ -0,0 +1,89 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# 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.
"""Aggregate interface."""
from novaclient import base
class Aggregate(base.Resource):
"""An aggregates is a collection of compute hosts."""
def __repr__(self):
return "<Aggregate: %s>" % self.id
def update(self, values):
"""Update the name and/or availability zone."""
return self.manager.update(self, values)
def add_host(self, host):
return self.manager.add_host(self, host)
def remove_host(self, host):
return self.manager.remove_host(self, host)
def set_metadata(self, metadata):
return self.manager.set_metadata(self, metadata)
def delete(self):
self.manager.delete(self)
class AggregateManager(base.ManagerWithFind):
resource_class = Aggregate
def list(self):
"""Get a list of os-aggregates."""
return self._list('/os-aggregates', 'aggregates')
def create(self, name, availability_zone):
"""Create a new aggregate."""
body = {'aggregate': {'name': name,
'availability_zone': availability_zone}}
return self._create('/os-aggregates', body, 'aggregate')
def get_details(self, aggregate):
"""Get details of the specified aggregate."""
return self._get('/os-aggregates/%s' % (base.getid(aggregate)),
"aggregate")
def update(self, aggregate, values):
"""Update the name and/or availablity zone."""
body = {'aggregate': values}
result = self._update("/os-aggregates/%s" % base.getid(aggregate),
body)
return self.resource_class(self, result["aggregate"])
def add_host(self, aggregate, host):
"""Add a host into the Host Aggregate."""
body = {'add_host': {'host': host}}
return self._create("/os-aggregates/%s/action" % base.getid(aggregate),
body, "aggregate")
def remove_host(self, aggregate, host):
"""Remove a host from the Host Aggregate."""
body = {'remove_host': {'host': host}}
return self._create("/os-aggregates/%s/action" % base.getid(aggregate),
body, "aggregate")
def set_metadata(self, aggregate, metadata):
"""Set a aggregate metadata, replacing the existing metadata."""
body = {'set_metadata': {'metadata': metadata}}
return self._create("/os-aggregates/%s/action" % base.getid(aggregate),
body, "aggregate")
def delete(self, aggregate):
"""Delete the specified aggregates."""
self._delete('/os-aggregates/%s' % (base.getid(aggregate)))

View File

@ -1,5 +1,6 @@
from novaclient import client
from novaclient.v1_1 import certs
from novaclient.v1_1 import aggregates
from novaclient.v1_1 import flavors
from novaclient.v1_1 import floating_ip_dns
from novaclient.v1_1 import floating_ips
@ -64,6 +65,7 @@ class Client(object):
self.usage = usage.UsageManager(self)
self.virtual_interfaces = \
virtual_interfaces.VirtualInterfaceManager(self)
self.aggregates = aggregates.AggregateManager(self)
# Add in any extensions...
if extensions:

View File

@ -380,6 +380,15 @@ def do_image_list(cs, args):
def do_image_meta(cs, args):
"""Set or Delete metadata on an image."""
image = _find_image(cs, args.image)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.images.set_meta(image, metadata)
elif args.action == 'delete':
cs.images.delete_meta(image, metadata.keys())
def _extract_metadata(args):
metadata = {}
for metadatum in args.metadata[0]:
# Can only pass the key in on 'delete'
@ -391,11 +400,7 @@ def do_image_meta(cs, args):
value = None
metadata[key] = value
if args.action == 'set':
cs.images.set_meta(image, metadata)
elif args.action == 'delete':
cs.images.delete_meta(image, metadata.keys())
return metadata
def _print_image(image):
@ -679,17 +684,7 @@ def do_image_create(cs, args):
def do_meta(cs, args):
"""Set or Delete metadata on a server."""
server = _find_server(cs, args.server)
metadata = {}
for metadatum in args.metadata[0]:
# Can only pass the key in on 'delete'
# So this doesn't have to have '='
if metadatum.find('=') > -1:
(key, value) = metadatum.split('=', 1)
else:
key = metadatum
value = None
metadata[key] = value
metadata = _extract_metadata(args)
if args.action == 'set':
cs.servers.set_meta(server, metadata)
@ -1298,7 +1293,7 @@ def do_secgroup_delete_group_rule(cs, args):
if (rule.get('ip_protocol') == params.get('ip_protocol') and
rule.get('from_port') == params.get('from_port') and
rule.get('to_port') == params.get('to_port') and
rule.get('group', {}).get('name') ==\
rule.get('group', {}).get('name') == \
params.get('group_name')):
return cs.security_group_rules.delete(rule['id'])
@ -1445,3 +1440,86 @@ def do_x509_get_root_cert(cs, args):
cacert = cs.certs.get()
cert.write(cacert.data)
print "Wrote x509 root cert to %s" % args.filename
def do_aggregate_list(cs, args):
"""Print a list of all aggregates."""
aggregates = cs.aggregates.list()
columns = ['Id', 'Name', 'Availability Zone', 'Operational State']
utils.print_list(aggregates, columns)
@utils.arg('name', metavar='<name>', help='Name of aggregate.')
@utils.arg('availability_zone', metavar='<availability_zone>',
help='The availablity zone of the aggregate.')
def do_aggregate_create(cs, args):
"""Create a new aggregate with the specified details."""
aggregate = cs.aggregates.create(args.name, args.availability_zone)
_print_aggregate_details(aggregate)
@utils.arg('id', metavar='<id>', help='Aggregate id to delete.')
def do_aggregate_delete(cs, args):
"""Delete the aggregate by its id."""
cs.aggregates.delete(args.id)
print "Aggregate %s has been succesfully deleted." % args.id
@utils.arg('id', metavar='<id>', help='Aggregate id to udpate.')
@utils.arg('name', metavar='<name>', help='Name of aggregate.')
@utils.arg('availability_zone', metavar='<availability_zone>',
help='The availablity zone of the aggregate.', nargs='?')
def do_aggregate_update(cs, args):
"""Update the aggregate's name and optionally availablity zone."""
updates = {"name": args.name}
if args.availability_zone:
updates["availability_zone"] = args.availability_zone
aggregate = cs.aggregates.update(args.id, updates)
print "Aggregate %s has been succesfully updated." % args.id
_print_aggregate_details(aggregate)
@utils.arg('id', metavar='<id>', help='Aggregate id to udpate.')
@utils.arg('metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help='Metadata to add/update to aggregate')
def do_aggregate_set_metadata(cs, args):
"""Update the metadata associated with the aggregate."""
metadata = _extract_metadata(args)
aggregate = cs.aggregates.set_metadata(args.id, metadata)
print "Aggregate %s has been succesfully updated." % args.id
_print_aggregate_details(aggregate)
@utils.arg('id', metavar='<id>', help='Host aggregate id to delete.')
@utils.arg('host', metavar='<host>', help='The host to add to the aggregate.')
def do_aggregate_add_host(cs, args):
"""Add the host to the specified aggregate."""
aggregate = cs.aggregates.add_host(args.id, args.host)
print "Aggregate %s has been succesfully updated." % args.id
_print_aggregate_details(aggregate)
@utils.arg('id', metavar='<id>', help='Host aggregate id to delete.')
@utils.arg('host', metavar='<host>', help='The host to add to the aggregate.')
def do_aggregate_remove_host(cs, args):
"""Remove the specified host from the specfied aggregate."""
aggregate = cs.aggregates.remove_host(args.id, args.host)
print "Aggregate %s has been succesfully updated." % args.id
_print_aggregate_details(aggregate)
@utils.arg('id', metavar='<id>', help='Host aggregate id to delete.')
def do_aggregate_details(cs, args):
"""Show details of the specified aggregate."""
_print_aggregate_details(cs.aggregates.get_details(args.id))
def _print_aggregate_details(aggregate):
columns = ['Id', 'Name', 'Availability Zone', 'Operational State',
'Hosts', 'Metadata']
utils.print_list([aggregate], columns)

View File

@ -708,3 +708,41 @@ class FakeHTTPClient(base_client.HTTPClient):
def post_os_certificates(self, **kw):
return (200, {'certificate': {'private_key': 'foo', 'data': 'bar'}})
#
# Aggregates
#
def get_os_aggregates(self, *kw):
return (200, {"aggregates": [
{'id':'1',
'name': 'test',
'availability_zone': 'nova1'},
{'id':'2',
'name': 'test2',
'availability_zone': 'nova1'},
]})
def _return_aggregate(self):
r = {'aggregate': self.get_os_aggregates()[1]['aggregates'][0]}
return (200, r)
def get_os_aggregates_1(self, **kw):
return self._return_aggregate()
def post_os_aggregates(self, body, **kw):
return self._return_aggregate()
def put_os_aggregates_1(self, body, **kw):
return self._return_aggregate()
def put_os_aggregates_2(self, body, **kw):
return self._return_aggregate()
def post_os_aggregates_1_action(self, body, **kw):
return self._return_aggregate()
def post_os_aggregates_2_action(self, body, **kw):
return self._return_aggregate()
def delete_os_aggregates_1(self, **kw):
return (202, None)

View File

@ -0,0 +1,129 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# 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 novaclient.v1_1 import aggregates
from tests import utils
from tests.v1_1 import fakes
cs = fakes.FakeClient()
class AggregatesTest(utils.TestCase):
def test_list_aggregates(self):
result = cs.aggregates.list()
cs.assert_called('GET', '/os-aggregates')
for aggregate in result:
self.assertTrue(isinstance(aggregate, aggregates.Aggregate))
def test_create_aggregate(self):
body = {"aggregate": {"name": "test", "availability_zone": "nova1"}}
aggregate = cs.aggregates.create("test", "nova1")
cs.assert_called('POST', '/os-aggregates', body)
self.assertTrue(isinstance(aggregate, aggregates.Aggregate))
def test_get_details(self):
aggregate = cs.aggregates.get_details("1")
cs.assert_called('GET', '/os-aggregates/1')
self.assertTrue(isinstance(aggregate, aggregates.Aggregate))
aggregate2 = cs.aggregates.get_details(aggregate)
cs.assert_called('GET', '/os-aggregates/1')
self.assertTrue(isinstance(aggregate2, aggregates.Aggregate))
def test_update(self):
aggregate = cs.aggregates.get_details("1")
values = {"name": "foo"}
body = {"aggregate": values}
result1 = aggregate.update(values)
cs.assert_called('PUT', '/os-aggregates/1', body)
self.assertTrue(isinstance(result1, aggregates.Aggregate))
result2 = cs.aggregates.update(2, values)
cs.assert_called('PUT', '/os-aggregates/2', body)
self.assertTrue(isinstance(result2, aggregates.Aggregate))
def test_update_with_availablity_zone(self):
aggregate = cs.aggregates.get_details("1")
values = {"name": "foo", "availability_zone": "new_zone"}
body = {"aggregate": values}
result3 = cs.aggregates.update(aggregate, values)
cs.assert_called('PUT', '/os-aggregates/1', body)
self.assertTrue(isinstance(result3, aggregates.Aggregate))
def test_add_host(self):
aggregate = cs.aggregates.get_details("1")
host = "host1"
body = {"add_host": {"host": "host1"}}
result1 = aggregate.add_host(host)
cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assertTrue(isinstance(result1, aggregates.Aggregate))
result2 = cs.aggregates.add_host("2", host)
cs.assert_called('POST', '/os-aggregates/2/action', body)
self.assertTrue(isinstance(result2, aggregates.Aggregate))
result3 = cs.aggregates.add_host(aggregate, host)
cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assertTrue(isinstance(result3, aggregates.Aggregate))
def test_remove_host(self):
aggregate = cs.aggregates.get_details("1")
host = "host1"
body = {"remove_host": {"host": "host1"}}
result1 = aggregate.remove_host(host)
cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assertTrue(isinstance(result1, aggregates.Aggregate))
result2 = cs.aggregates.remove_host("2", host)
cs.assert_called('POST', '/os-aggregates/2/action', body)
self.assertTrue(isinstance(result2, aggregates.Aggregate))
result3 = cs.aggregates.remove_host(aggregate, host)
cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assertTrue(isinstance(result3, aggregates.Aggregate))
def test_set_metadata(self):
aggregate = cs.aggregates.get_details("1")
metadata = {"foo": "bar"}
body = {"set_metadata": {"metadata": metadata}}
result1 = aggregate.set_metadata(metadata)
cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assertTrue(isinstance(result1, aggregates.Aggregate))
result2 = cs.aggregates.set_metadata(2, metadata)
cs.assert_called('POST', '/os-aggregates/2/action', body)
self.assertTrue(isinstance(result2, aggregates.Aggregate))
result3 = cs.aggregates.set_metadata(aggregate, metadata)
cs.assert_called('POST', '/os-aggregates/1/action', body)
self.assertTrue(isinstance(result3, aggregates.Aggregate))
def test_delete_aggregate(self):
aggregate = cs.aggregates.list()[0]
aggregate.delete()
cs.assert_called('DELETE', '/os-aggregates/1')
cs.aggregates.delete('1')
cs.assert_called('DELETE', '/os-aggregates/1')
cs.aggregates.delete(aggregate)
cs.assert_called('DELETE', '/os-aggregates/1')

View File

@ -387,3 +387,47 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/flavors', body, pos=-2)
self.assert_called('GET', '/flavors/1')
def test_aggregate_list(self):
self.run_command('aggregate-list')
self.assert_called('GET', '/os-aggregates')
def test_aggregate_create(self):
self.run_command('aggregate-create test_name nova1')
body = {"aggregate": {"name": "test_name",
"availability_zone": "nova1"}}
self.assert_called('POST', '/os-aggregates', body)
def test_aggregate_delete(self):
self.run_command('aggregate-delete 1')
self.assert_called('DELETE', '/os-aggregates/1')
def test_aggregate_update(self):
self.run_command('aggregate-update 1 new_name')
body = {"aggregate": {"name": "new_name"}}
self.assert_called('PUT', '/os-aggregates/1', body)
def test_aggregate_update_with_availablity_zone(self):
self.run_command('aggregate-update 1 foo new_zone')
body = {"aggregate": {"name": "foo", "availability_zone": "new_zone"}}
self.assert_called('PUT', '/os-aggregates/1', body)
def test_aggregate_set_metadata(self):
self.run_command('aggregate-set-metadata 1 foo=bar delete_key')
body = {"set_metadata": {"metadata": {"foo": "bar",
"delete_key": None}}}
self.assert_called('POST', '/os-aggregates/1/action', body)
def test_aggregate_add_host(self):
self.run_command('aggregate-add-host 1 host1')
body = {"add_host": {"host": "host1"}}
self.assert_called('POST', '/os-aggregates/1/action', body)
def test_aggregate_remove_host(self):
self.run_command('aggregate-remove-host 1 host1')
body = {"remove_host": {"host": "host1"}}
self.assert_called('POST', '/os-aggregates/1/action', body)
def test_aggregate_details(self):
self.run_command('aggregate-details 1')
self.assert_called('GET', '/os-aggregates/1')