diff --git a/etc/graffiti.conf.sample b/etc/graffiti.conf.sample index 35f6ebb..f94a41d 100644 --- a/etc/graffiti.conf.sample +++ b/etc/graffiti.conf.sample @@ -5,3 +5,7 @@ persistence_type=memory [FILE_PERSISTENCE] dictionary_folder=/tmp/graffiti-dictionary/ + +[DATABASE] +#connection = sqlite:////var/tmp/graffiti.db +#connection = mysql://graffiti:graffiti@127.0.0.1:3306/graffiti diff --git a/graffiti/api/controllers/v1/capability_type.py b/graffiti/api/controllers/v1/capability_type.py index 5b3c7b3..9c7c3bd 100644 --- a/graffiti/api/controllers/v1/capability_type.py +++ b/graffiti/api/controllers/v1/capability_type.py @@ -18,6 +18,8 @@ from pecan.rest import RestController from wsme.api import Response from wsmeext.pecan import wsexpose +from graffiti.api.controllers.v1.capability_type_batch import\ + CapabilityTypeBatchController from graffiti.api.controllers.v1.capability_type_derived import\ CapabilityTypeDerivedController from graffiti.api.model.v1.capability_type import CapabilityType @@ -32,6 +34,7 @@ import six class CapabilityTypeController(RestController): + batch = CapabilityTypeBatchController() derived_properties = CapabilityTypeDerivedController() def __init__(self): diff --git a/graffiti/api/controllers/v1/capability_type_batch.py b/graffiti/api/controllers/v1/capability_type_batch.py new file mode 100644 index 0000000..621cfed --- /dev/null +++ b/graffiti/api/controllers/v1/capability_type_batch.py @@ -0,0 +1,102 @@ +# Copyright (c) 2014 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 graffiti.api.model.v1.capability_type import CapabilityType +from graffiti.api.model.v1.dao.captype_dao_factory \ + import CapabilityTypeDAOFactory +from graffiti.api.model.v1.dao.ns_dao_factory import NSDAOFactory + +from graffiti.common import exception as exc +from graffiti.common.utilities.capability_type_tree import CapabilityTypeTree + +from oslo.config import cfg +from pecan.rest import RestController +from wsmeext.pecan import wsexpose + + +class CapabilityTypeBatchController(RestController): + def __init__(self): + super(RestController, self).__init__() + self.status = 200 + self._cap_controller = None + self._ns_controller = None + self._load_controller() + + def _load_controller(self): + dao_type = cfg.CONF.DEFAULT.persistence_type + self._cap_controller = CapabilityTypeDAOFactory.create(dao_type) + self._ns_controller = NSDAOFactory.get() + + @wsexpose + def options(self): + pass + + @wsexpose([CapabilityType], body=[CapabilityType]) + def post(self, capability_types): + """Batch create capability types + @param capability_types: list of CapabilityTypes + """ + + cap_types = [] + # Verify all namespaces exists + self.__verify_namespaces(capability_types) + + tree = CapabilityTypeTree() + tree.build(capability_types) + + # TODO(wko): verify external derived roots + # self.__verify_external_derived_roots_exist( + # tree.types_with_external_root) + + for cap_key, cap_node in tree.root_types.iteritems(): + self.create_capability_type_recursively(cap_node, cap_types) + + return cap_types + + def create_capability_type_recursively(self, tree_node, cap_types): + if tree_node: + capability_type = tree_node.cap_type + exists_ct = self._cap_controller.get_capability_type( + capability_type.name, capability_type.namespace) + + if exists_ct: + # update + self._cap_controller.put_capability_type( + capability_type.name, capability_type.namespace, + capability_type + ) + cap_types.append(capability_type) + else: + # add + new_ct = self._cap_controller.set_capability_type( + capability_type) + cap_types.append(new_ct) + + for cap_key, child_node in tree_node.children.iteritems(): + self.create_capability_type_recursively(child_node, cap_types) + + def __verify_namespaces(self, capability_types): + namespaces = [] + for ct in capability_types: + if ct.namespace not in namespaces: + namespaces.append(ct.namespace) + + found_namespace = False + for namespace in namespaces: + found_namespace = self._ns_controller.get_namespace(namespace) + if not found_namespace: + raise exc.NotFound("namespace:{0} - does not exist". + format(namespace)) diff --git a/graffiti/api/model/v1/capability_type_key.py b/graffiti/api/model/v1/capability_type_key.py new file mode 100644 index 0000000..3eb1d02 --- /dev/null +++ b/graffiti/api/model/v1/capability_type_key.py @@ -0,0 +1,33 @@ +# Copyright (c) 2014 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 wsme +from wsme import types + + +class CapabilityTypeKey(types.Base): + name = wsme.wsattr(types.text, mandatory=True) + namespace = wsme.wsattr(types.text, mandatory=True) + + _wsme_attr_order = ('name', 'namespace') + + def __init__(self, **kwargs): + super(CapabilityTypeKey, self).__init__(**kwargs) + + def __hash__(self): + return hash((self.name, self.namespace)) + + def __eq__(self, other): + return (self.name, self.namespace) == (other.name, other.namespace) diff --git a/graffiti/api/model/v1/dao/captype_db_dao.py b/graffiti/api/model/v1/dao/captype_db_dao.py index 91fb74e..4200d6c 100644 --- a/graffiti/api/model/v1/dao/captype_db_dao.py +++ b/graffiti/api/model/v1/dao/captype_db_dao.py @@ -109,9 +109,7 @@ class DBCapabilityTypeDAO(CapabilityTypeDAOBase): def get_capability_type(self, name, namespace): db_capability_type = dbapi.capability_type_get(name, namespace) if not db_capability_type: - res = CapabilityType(CapabilityType(), status_code=404, - error="CapabilityType Not Found") - return res + return None return self._to_model(db_capability_type) diff --git a/graffiti/common/utilities/__init__.py b/graffiti/common/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graffiti/common/utilities/capability_type_tree.py b/graffiti/common/utilities/capability_type_tree.py new file mode 100644 index 0000000..a69a537 --- /dev/null +++ b/graffiti/common/utilities/capability_type_tree.py @@ -0,0 +1,133 @@ +# Copyright (c) 2014 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 graffiti.api.model.v1.capability_type_key import CapabilityTypeKey +from graffiti.common.utilities.capability_type_tree_node \ + import CapabilityTypeTreeNode + + +class CapabilityTypeTree(): + + def __init__(self, **kwargs): + self.root_types = {} + self.types_with_external_root = {} + + def build(self, capability_type_list): + capability_types = {} + for cap_type in capability_type_list: + # todo(wko): raise error if duplicate found + key = CapabilityTypeKey(name=cap_type.name, + namespace=cap_type.namespace) + capability_types[key] = cap_type + + # Handle: 1) No parent 2) No children (no-op) + # 3) Parent in tree + # 4) Parent not processed, yet + # 5) More than one root in input (not common ancestor) + # 6) Type has parent, but not in input (external parent) + types_with_unmapped_parents = {} + for current_key, current_cap_type in capability_types.iteritems(): + current_node = CapabilityTypeTreeNode() + current_node.cap_type = current_cap_type + + # Scenario 1 & 5 + if not current_cap_type.derived_from \ + or not current_cap_type.derived_from.name: + self.root_types[current_key] = current_node + continue + + if self.insert_node(self.root_types, current_key, current_node): + # Scenario 3 + continue + elif self.insert_node(types_with_unmapped_parents, + current_key, current_node): + # Scenario 3 (not converged) + continue + else: + # Scenario 4 part a + types_with_unmapped_parents[current_key] = current_node + + # Scenario 4 part b + # Perform eventual convergence on roots + self.converge_unmapped_types(self.root_types, + types_with_unmapped_parents) + + # Perform eventual convergence on types_with_unmapped_parents + self.converge_unmapped_types(types_with_unmapped_parents, + types_with_unmapped_parents) + + # Scenario 6 + self.types_with_external_root.update(types_with_unmapped_parents) + + def insert_node(self, cap_types, current_key, current_node): + # For each cap_type + # if it is the parent of the current_node.cap_type + # set root_node as the current_node.parent_node + # add the current_node to root_node.children + # break + # else + # recursively check if parent is in root_node.children + # break if found + result = False + if cap_types: + i = 0 + for root_key, root_node in cap_types.iteritems(): + # todo(wko): derived_from should be a CapabilityTypeKey + current_parent_name = current_node.cap_type.derived_from.name + current_parent_namesp = \ + current_node.cap_type.derived_from.namespace + + if root_key.name == current_parent_name and\ + root_key.namespace == current_parent_namesp: + current_node.parent_node = root_node + root_node.children[current_key] = current_node + result = True + break + + result = self.insert_node(root_node.children, current_key, + current_node) + if result: + break + i += 1 + + return result + + def converge_unmapped_types(self, root_types, types_with_unmapped_parents): + + previous_loop_unmapped_parents = 0 + num_loops_without_change = 0 + + while len(types_with_unmapped_parents) > 0 \ + and num_loops_without_change < 2: + types_with_found_parent = [] + for unmapped_key, unmapped_node in \ + types_with_unmapped_parents.iteritems(): + result = self.insert_node(root_types, unmapped_key, + unmapped_node) + if result: + types_with_found_parent.append(unmapped_key) + continue + + for mapped_parent in types_with_found_parent: + del types_with_unmapped_parents[mapped_parent] + + this_loop_unmapped_parents = len(types_with_unmapped_parents) + if previous_loop_unmapped_parents == this_loop_unmapped_parents: + num_loops_without_change += 1 + else: + num_loops_without_change = 0 + + previous_loop_unmapped_parents = this_loop_unmapped_parents diff --git a/graffiti/common/utilities/capability_type_tree_node.py b/graffiti/common/utilities/capability_type_tree_node.py new file mode 100644 index 0000000..1f403e5 --- /dev/null +++ b/graffiti/common/utilities/capability_type_tree_node.py @@ -0,0 +1,22 @@ +# Copyright (c) 2014 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. + + +class CapabilityTypeTreeNode(): + + def __init__(self, **kwargs): + self.children = {} + self.parent_node = None + self.cap_type = None