Modifying CRD return type from dict to object
- Adding warlock library and json-schema dependency - Adding resources directory which contains cluster object schema - Modifying unit test cases - Changing doc content Change-Id: I54e4ee316a8cac45ce75ea5d528211c96ef83e25
This commit is contained in:
parent
8c47aa654b
commit
b3024868c3
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema",
|
||||
"name": "Cluster",
|
||||
"title": "Cluster",
|
||||
"type" : "object",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Cluster Identifier",
|
||||
"pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Cluster name",
|
||||
"required": true
|
||||
},
|
||||
"network_id": {
|
||||
"type": "string",
|
||||
"description": "Network Identifier",
|
||||
"required": true
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Cluster status"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"description": "Date and time of cluster creation",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"description": "Date and time of cluster update",
|
||||
"format": "date-time"
|
||||
},
|
||||
"end_points": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"description": "Cluster endpoints"
|
||||
},
|
||||
"flavor": {
|
||||
"type": "string",
|
||||
"description": "Cluster flavor",
|
||||
"required": true
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "Cluster size",
|
||||
"required": true
|
||||
},
|
||||
"volume_size": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"description": "Cluster volume"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
|
@ -57,8 +57,7 @@ class TestCreateCluster(base.TestCueBase):
|
|||
('flavor', self.cluster_flavor),
|
||||
('size', self.cluster_size)
|
||||
]
|
||||
response = {"cluster": {
|
||||
"id": "222",
|
||||
response = {"id": "222",
|
||||
"project_id": "test",
|
||||
"network_id": self.cluster_network_id,
|
||||
"name": self.cluster_name,
|
||||
|
@ -69,7 +68,7 @@ class TestCreateCluster(base.TestCueBase):
|
|||
"deleted": "0",
|
||||
"created_at": "2015-02-04 00:35:02",
|
||||
"updated_at": "2015-02-04 00:35:02",
|
||||
"deleted_at": ""}}
|
||||
"deleted_at": ""}
|
||||
|
||||
mocker = mock.Mock(return_value=response)
|
||||
self.app.client_manager.mq.clusters.create = mocker
|
||||
|
@ -162,7 +161,7 @@ class TestShowCluster(base.TestCueBase):
|
|||
"""test show cluster with correct cluster id"""
|
||||
cluster_id = 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018'
|
||||
|
||||
response = {"cluster": {
|
||||
response = {
|
||||
"id": cluster_id,
|
||||
"project_id": "test",
|
||||
"network_id": "26477575",
|
||||
|
@ -170,7 +169,7 @@ class TestShowCluster(base.TestCueBase):
|
|||
"status": "BUILDING",
|
||||
"flavor": "1",
|
||||
"size": "2",
|
||||
"volume_size": "1024"}
|
||||
"volume_size": "1024"
|
||||
}
|
||||
|
||||
arglist = [cluster_id]
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
import json
|
||||
import os
|
||||
|
||||
import pkg_resources
|
||||
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
"""Search for the first defined of possibly many env vars
|
||||
|
@ -50,3 +53,32 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
|
|||
else:
|
||||
row.append(data)
|
||||
return tuple(row)
|
||||
|
||||
|
||||
def resource_string(*args, **kwargs):
|
||||
"""Return specified resource as a string"""
|
||||
if len(args) == 0:
|
||||
raise ValueError()
|
||||
|
||||
package = kwargs.pop('package', None)
|
||||
|
||||
if not package:
|
||||
package = 'cueclient'
|
||||
|
||||
resource_path = os.path.join('resources', *args)
|
||||
|
||||
if not pkg_resources.resource_exists(package, resource_path):
|
||||
# TODO(ap): add exceptions
|
||||
# raise exceptions.ResourceNotFound('Could not find the requested '
|
||||
# 'resource: %s' % resource_path)
|
||||
pass
|
||||
|
||||
return pkg_resources.resource_string(package, resource_path)
|
||||
|
||||
|
||||
def load_schema(version, name, package=None):
|
||||
"""Load json schema from resources"""
|
||||
schema_string = resource_string('schemas', version, '%s.json' % name,
|
||||
package=package)
|
||||
|
||||
return json.loads(schema_string)
|
|
@ -59,11 +59,11 @@ class ShowClusterCommand(show.ShowOne):
|
|||
|
||||
data = client.clusters.get(parsed_args.id)
|
||||
|
||||
return zip(*sorted(six.iteritems(data['cluster'])))
|
||||
return zip(*sorted(six.iteritems(data)))
|
||||
|
||||
|
||||
class CreateClusterCommand(show.ShowOne):
|
||||
"""Create Domain"""
|
||||
"""Create Cluster"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateClusterCommand, self).get_parser(prog_name)
|
||||
|
@ -87,7 +87,7 @@ class CreateClusterCommand(show.ShowOne):
|
|||
size=parsed_args.size,
|
||||
volume_size=parsed_args.volume_size)
|
||||
|
||||
return zip(*sorted(six.iteritems(data['cluster'])))
|
||||
return zip(*sorted(six.iteritems(data)))
|
||||
|
||||
|
||||
class SetClusterCommand(command.Command):
|
||||
|
|
|
@ -14,10 +14,16 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from cueclient import controller
|
||||
from cueclient import utils
|
||||
from cueclient import warlock
|
||||
|
||||
Cluster = warlock.model_factory(utils.load_schema('v1', 'cluster'))
|
||||
|
||||
|
||||
class ClusterController(controller.Controller):
|
||||
"""Cluster Controller to manages operations."""
|
||||
def create(self, name, nic, flavor, size, volume_size):
|
||||
"""Create Cluster"""
|
||||
data = {
|
||||
"network_id": nic,
|
||||
"name": name,
|
||||
|
@ -25,20 +31,22 @@ class ClusterController(controller.Controller):
|
|||
"size": size,
|
||||
"volume_size": volume_size
|
||||
}
|
||||
|
||||
url = self.build_url("/clusters")
|
||||
|
||||
return self._post(url, json=data)
|
||||
return Cluster(self._post(url, json=data)['cluster'])
|
||||
|
||||
def list(self, marker=None, limit=None, params=None):
|
||||
"""List Clusters"""
|
||||
url = self.build_url("/clusters", marker, limit, params)
|
||||
|
||||
return self._get(url, "clusters")
|
||||
response = self._get(url, "clusters")
|
||||
return [Cluster(i) for i in response]
|
||||
|
||||
def get(self, cluster_id):
|
||||
"""Show Cluster"""
|
||||
url = self.build_url("/clusters/%s" % cluster_id)
|
||||
|
||||
return self._get(url)
|
||||
return Cluster(self._get(url)['cluster'])
|
||||
|
||||
def update(self, cluster_id, values):
|
||||
data = {
|
||||
|
@ -50,6 +58,7 @@ class ClusterController(controller.Controller):
|
|||
return self._patch(url, data=data)
|
||||
|
||||
def delete(self, cluster_id):
|
||||
"""Delete Cluster"""
|
||||
url = self.build_url("/clusters/%s" % cluster_id)
|
||||
|
||||
return self._delete(url)
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
# Copyright 2012 Brian Waldon
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Code copied from Warlock, as warlock depends on jsonschema==0.2
|
||||
# Hopefully we can upstream the changes ASAP.
|
||||
#
|
||||
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import jsonschema
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InvalidOperation(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def model_factory(schema):
|
||||
"""Generate a model class based on the provided JSON Schema
|
||||
|
||||
:param schema: dict representing valid JSON schema
|
||||
"""
|
||||
schema = copy.deepcopy(schema)
|
||||
|
||||
def validator(obj):
|
||||
"""Apply a JSON schema to an object"""
|
||||
try:
|
||||
jsonschema.validate(obj, schema, cls=jsonschema.Draft3Validator)
|
||||
except jsonschema.ValidationError as e:
|
||||
raise ValidationError(str(e))
|
||||
|
||||
class Model(dict):
|
||||
"""Self-validating model for arbitrary objects"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
d = dict(*args, **kwargs)
|
||||
|
||||
# we overload setattr so set this manually
|
||||
self.__dict__['validator'] = validator
|
||||
try:
|
||||
self.validator(d)
|
||||
except ValidationError as e:
|
||||
raise ValueError('Validation Error: %s' % str(e))
|
||||
else:
|
||||
dict.__init__(self, d)
|
||||
|
||||
self.__dict__['changes'] = {}
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return self.__getitem__(key)
|
||||
except KeyError:
|
||||
raise AttributeError(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
mutation = dict(self.items())
|
||||
mutation[key] = value
|
||||
try:
|
||||
self.validator(mutation)
|
||||
except ValidationError as e:
|
||||
raise InvalidOperation(str(e))
|
||||
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
self.__dict__['changes'][key] = value
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
self.__setitem__(key, value)
|
||||
|
||||
def clear(self):
|
||||
raise InvalidOperation()
|
||||
|
||||
def pop(self, key, default=None):
|
||||
raise InvalidOperation()
|
||||
|
||||
def popitem(self):
|
||||
raise InvalidOperation()
|
||||
|
||||
def __delitem__(self, key):
|
||||
raise InvalidOperation()
|
||||
|
||||
# NOTE(termie): This is kind of the opposite of what copy usually does
|
||||
def copy(self):
|
||||
return copy.deepcopy(dict(self))
|
||||
|
||||
def update(self, other):
|
||||
# NOTE(kiall): It seems update() doesn't update the
|
||||
# self.__dict__['changes'] dict correctly.
|
||||
mutation = dict(self.items())
|
||||
mutation.update(other)
|
||||
try:
|
||||
self.validator(mutation)
|
||||
except ValidationError as e:
|
||||
raise InvalidOperation(str(e))
|
||||
dict.update(self, other)
|
||||
|
||||
def iteritems(self):
|
||||
return copy.deepcopy(dict(self)).iteritems()
|
||||
|
||||
def items(self):
|
||||
return copy.deepcopy(dict(self)).items()
|
||||
|
||||
def itervalues(self):
|
||||
return copy.deepcopy(dict(self)).itervalues()
|
||||
|
||||
def keys(self):
|
||||
return copy.deepcopy(dict(self)).keys()
|
||||
|
||||
def values(self):
|
||||
return copy.deepcopy(dict(self)).values()
|
||||
|
||||
@property
|
||||
def changes(self):
|
||||
return copy.deepcopy(self.__dict__['changes'])
|
||||
|
||||
Model.__name__ = str(schema['title'])
|
||||
return Model
|
|
@ -32,15 +32,14 @@ the bindings.
|
|||
# Create an instance of the client
|
||||
cue_client = client.Client(session=session)
|
||||
|
||||
# Cluster List
|
||||
# Cluster List - returns list of cluster objects
|
||||
list_response = cue_client.clusters.list()
|
||||
|
||||
# Iterate the list, printing some useful information
|
||||
for cluster in list_response:
|
||||
|
||||
print "Cluster ID: %s \t Name: %s \t NetworkId: %s \t Flavor: %s \t Size: %s" % \
|
||||
(cluster['id'], cluster['name'], cluster['network_id'],
|
||||
cluster['flavor'], cluster['size'])
|
||||
(cluster.id, cluster.name, cluster.network_id, cluster.flavor, cluster.size)
|
||||
|
||||
And the output this program might produce:
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
cliff>=1.7.0 # Apache-2.0
|
||||
jsonschema>=2.0.0,<3.0.0
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
python-keystoneclient>=0.11.1
|
||||
requests>=2.2.0,!=2.4.0
|
||||
|
|
Loading…
Reference in New Issue