helix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ka...@apache.org
Subject [2/4] helix git commit: adding python helix admin library
Date Mon, 08 Dec 2014 03:02:53 GMT
adding python helix admin library


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/e2a033f8
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/e2a033f8
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/e2a033f8

Branch: refs/heads/master
Commit: e2a033f8563538a4cae1e73a3a7f95a302751a98
Parents: af77829
Author: Jon Heise <jheise@jheise-ld1.linkedin.biz>
Authored: Thu Dec 4 15:38:20 2014 -0800
Committer: Jon Heise <jheise@jheise-ld1.linkedin.biz>
Committed: Thu Dec 4 15:38:20 2014 -0800

----------------------------------------------------------------------
 contributors/py-helix-admin/helix/__init__.py   |   0
 contributors/py-helix-admin/helix/cluster.py    | 346 ++++++++++++
 contributors/py-helix-admin/helix/functions.py  | 543 +++++++++++++++++++
 .../py-helix-admin/helix/helixexceptions.py     |  16 +
 .../py-helix-admin/helix/participant.py         |  66 +++
 contributors/py-helix-admin/helix/partition.py  |  18 +
 .../py-helix-admin/helix/resourcegroup.py       |  34 ++
 7 files changed, 1023 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/__init__.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/__init__.py b/contributors/py-helix-admin/helix/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/cluster.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/cluster.py b/contributors/py-helix-admin/helix/cluster.py
new file mode 100644
index 0000000..f2b931e
--- /dev/null
+++ b/contributors/py-helix-admin/helix/cluster.py
@@ -0,0 +1,346 @@
+"""base class for anything that connects to helix"""
+
+from participant import Participant
+from partition import Partition
+from resourcegroup import ResourceGroup
+
+from helixexceptions import HelixException
+import functions
+
+
+class Cluster(object):
+    """Basic model of a cluster, holds participants, partitions, slices,
+    external view, ideal state, resource groups"""
+    ver = (1, 0)
+
+    def __init__(self, host, cluster):
+        super(Cluster, self).__init__()
+        self.host = host
+        self.cluster = cluster
+
+        # dynamically loaded data below
+        self._partitions = {}
+        self._participants = {}
+        self._resources = {}
+        self._ideal_state = {}
+        self._external_view = {}
+
+    def __str__(self):
+        return "{0} Object for {1}".format(self.__class__.__name__,
+                                           self.cluster)
+
+    def __repr__(self):
+        return "{0}({1}, {2})".format(self.__class__.__name__, self.cluster,
+                                      self.host)
+
+    def load_resources(self):
+        """queries helix for resource groups and loades them into model"""
+        try:
+            for cur_resource in functions.get_resource_groups(self.host,
+                                                              self.cluster):
+                data = functions.get_resource_group(self.host, self.cluster,
+                                                    cur_resource)
+                name = data["id"]
+                count = data["simpleFields"]["NUM_PARTITIONS"]
+                replicas = data["simpleFields"]["REPLICAS"]
+                statemode = data["simpleFields"]["STATE_MODEL_DEF_REF"]
+                resource = ResourceGroup(name,
+                                         count, replicas,
+                                         statemode, data)
+                partitions = data["mapFields"]
+                for part, hosts in partitions.items():
+                    phosts = []
+                    for host, status in hosts.items():
+                        participant = self.participants[host]
+                        participant.partitions[part] = status
+                        phosts.append(participant)
+
+                    partition = Partition(part, phosts)
+                    resource.add_partition(partition)
+
+                self._resources[cur_resource] = resource
+        except HelixException:
+            pass
+
+    @property
+    def resources(self):
+        """sanely handle resource loading and usage"""
+        if not self._resources:
+            self.load_resources()
+        return self._resources
+
+    @resources.setter
+    def resources(self, value):
+        """ensure an exception is raise on an attempt to set resource groups"""
+        raise HelixException("Resource groups cannont be added in this manner")
+
+    def _cluster_exists(self):
+        """verify cluster exists in helix"""
+        if self.cluster in functions.get_clusters(self.host):
+            return True
+        return False
+
+    def load_participants(self):
+        """create instances of storage node for participants in this cluster"""
+        self._participants = {}
+
+        try:
+            instances = functions.get_instances(self.host, self.cluster)
+            for instance in instances:
+                ident = instance["id"]
+                enabled = instance["simpleFields"]["HELIX_ENABLED"]
+                alive = instance["simpleFields"]["Alive"]
+                data = instance
+                participant = Participant(ident, alive, enabled, data)
+                self._participants[instance["id"]] = participant
+        except HelixException:
+            pass
+
+    @property
+    def participants(self):
+        """returns participants, if not loaded, loads them then returns"""
+        if not self._participants:
+            self.load_participants()
+        return self._participants
+
+    @participants.setter
+    def participants(self, value):
+        raise HelixException("Participants cannot added in this fashion!")
+
+    def load_partitions(self):
+        """query partitions from helix and load into model"""
+        self._partitions = {}
+        for resource in self.resources:
+            newstate = functions.get_ideal_state(self.host, self.cluster,
+                                                 resource)
+            self._partitions[resource] = {}
+            if newstate:
+                for part in newstate:
+                    hosts = [self.participants[x] for x in newstate[part]]
+                    partition = Partition(part, hosts)
+                    self._partitions[resource][part] = partition
+                    for host in newstate[part]:
+                        self.participants[host].partitions[part] = partition
+
+    @property
+    def partitions(self):
+        """return partitions"""
+        if not self._partitions:
+            self.load_partitions()
+        return self._partitions
+
+    def load_ideal_state(self):
+        """query ideal state from helix and load into model"""
+        self._ideal_state = {}
+        for resource in self.resources:
+            self._ideal_state[resource] = \
+                functions.get_ideal_state(self.host, self.cluster, resource)
+
+    @property
+    def ideal_state(self):
+        """return ideal state"""
+        if not self._ideal_state:
+            self.load_ideal_state()
+        return self._ideal_state
+
+    @ideal_state.setter
+    def ideal_state(self, value):
+        """setter for ideal state"""
+        raise HelixException("Cannot adjust Ideal State in this manner")
+
+    def load_external_view(self):
+        """query external view from helix and load into model"""
+        self._external_view = {}
+        for resource in self.resources:
+            self._external_view[resource] = \
+                functions.get_external_view(self.host, self.cluster, resource)
+
+    @property
+    def external_view(self):
+        """return external view"""
+        if not self._external_view:
+            self.load_external_view()
+        return self._external_view
+
+    @external_view.setter
+    def external_view(self, value):
+        """setter for external view"""
+        raise HelixException("External View cannot be modified!")
+
+    def get_config(self, config):
+        """ get requested config from helix"""
+        return functions.get_config(self.host, self.cluster, config)
+
+    def set_cluster_config(self, config):
+        """ set given configs in helix"""
+        return functions.set_config(self.host, self.cluster, config)
+
+    def set_resource_config(self, config, resource):
+        """ set given configs in helix"""
+        rname = resource
+        if isinstance(resource, ResourceGroup):
+            rname = resource.name
+        return functions.set_config(self.host, self.cluster, config,
+                                    resource=rname)
+
+    def set_participant_config(self, config, participant):
+        pname = participant
+        if isinstance(participant, Participant):
+            pname = participant.ident
+        """ set given configs in helix"""
+        return functions.set_config(self.host, self.cluster, config,
+                                    participant=pname)
+
+    def activate_cluster(self, grand, enabled=True):
+        """activate this cluster with the specified grand cluster"""
+        return functions.activate_cluster(self.host, self.cluster, grand,
+                                          enabled)
+
+    def deactivate_cluster(self, grand):
+        """deactivate this cluster against the given grandcluster"""
+        return functions.deactivate_cluster(self.host, self.cluster, grand)
+
+    def add_cluster(self):
+        """add cluster to helix"""
+        return functions.add_cluster(self.host, self.cluster)
+
+    def add_instance(self, instances, port):
+        """add instance to cluster"""
+        return functions.add_instance(self.host, self.cluster, instances, port)
+
+    def rebalance(self, resource, replicas, key=""):
+        """rebalance a resource group"""
+        return functions.rebalance(self.host, self.cluster, resource,
+                                   replicas, key)
+
+    def add_resource(self, resource, partitions, state_model_def, mode=""):
+        """add resource to cluster"""
+        return functions.add_resource(self.host, self.cluster, resource,
+                                      partitions, state_model_def, mode)
+
+    def enable_instance(self, instance, enabled=True):
+        """enable instance, assumes instance a participant object"""
+        ident = None
+        if isinstance(instance, Participant):
+            ident = instance.ident
+        elif isinstance(instance, str):
+            ident = instance
+        else:
+            raise HelixException("Instance must be a string or participant")
+        return functions.enable_instance(self.host, self.cluster, ident,
+                                         enabled)
+
+    def disable_instance(self, instance):
+        """disable instance, assumes instance is a participant object"""
+        return self.enable_instance(instance, enabled=False)
+
+    def enable_partition(self, resource, partition, instance, enabled=True):
+        """enable partition, assumes instance and partition are
+        helix objects"""
+        ident = None
+        part_id = None
+
+        if isinstance(instance, Participant):
+            ident = instance.ident
+        elif isinstance(instance, str):
+            ident = instance
+        else:
+            raise HelixException("Instance must be a string or participant")
+
+        if isinstance(partition, Partition):
+            part_id = partition.name
+        elif isinstance(partition, str):
+            part_id = partition
+        else:
+            raise HelixException("Partition must be a string or partition")
+
+        return functions.enable_partition(self.host, self.cluster, resource,
+                                          part_id, ident, enabled)
+
+    def disable_partition(self, resource, partition, instance):
+        """disable partition, conveience function for enable partition"""
+        return self.enable_partition(resource, partition, instance,
+                                     enabled=False)
+
+    def enable_resource(self, resource, enabled=True):
+        """enable/disable resource"""
+        resource_name = None
+        if isinstance(resource, ResourceGroup):
+            resource_name = resource.name
+        elif isinstance(resource, str):
+            resource_name = resource
+        else:
+            raise HelixException(
+                "Resource must be a string or a resource group object")
+
+        return functions.enable_resource(self.host, self.cluster,
+                                         resource_name, enabled)
+
+    def disable_resource(self, resource):
+        """disable given function"""
+        return self.enable_resource(resource, enabled=False)
+
+    def add_resource_tag(self, resource, tag):
+        """add a tag to a resource"""
+        resource_name = None
+        if isinstance(resource, ResourceGroup):
+            resource_name = resource.name
+        elif isinstance(resource, str):
+            resource_name = resource
+        else:
+            raise HelixException("Resource must be resource object or string")
+
+        return functions.add_resource_tag(self.host, self.cluster,
+                                          resource_name, tag)
+
+    # del resource not yet available in api
+    # def del_resource_tag(self, resource, tag):
+    # """del a tag to a resource"""
+    #     resource_name = None
+    #     if isinstance(resource, ResourceGroup):
+    #         resource_name = resource.name
+    #     elif isinstance(resource, str):
+    #         resource_name = resource
+    #     else:
+    #         raise HelixException("Resource must be resource object or str")
+    #
+    #     return functions.del_resource_tag(self.host, self.cluster,
+    #                                       resource_name, tag)
+
+    def add_instance_tag(self, instance, tag):
+        ident = None
+
+        if isinstance(instance, Participant):
+            ident = instance.ident
+        elif isinstance(instance, str):
+            ident = instance
+        else:
+            raise HelixException("Instance must be a string or participant")
+
+        return functions.add_instance_tag(self.host, self.cluster, ident, tag)
+
+    def del_instance_tag(self, instance, tag):
+        ident = None
+
+        if isinstance(instance, Participant):
+            ident = instance.ident
+        elif isinstance(instance, str):
+            ident = instance
+        else:
+            raise HelixException("Instance must be a string or participant")
+
+        return functions.del_instance_tag(self.host, self.cluster, ident, tag)
+
+    def del_instance(self, instance):
+        """remove instance from cluster, assumes instance is a
+        participant object"""
+        return functions.del_instance(self.host, self.cluster, instance.ident)
+
+    def del_resource(self, resource):
+        """remove resource group from cluster, assumes resource is a
+        resource object"""
+        return functions.del_resource(self.host, self.cluster, resource.name)
+
+    def del_cluster(self):
+        """remove cluster from helix"""
+        return functions.del_cluster(self.host, self.cluster)

http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/functions.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/functions.py b/contributors/py-helix-admin/helix/functions.py
new file mode 100644
index 0000000..b66cca7
--- /dev/null
+++ b/contributors/py-helix-admin/helix/functions.py
@@ -0,0 +1,543 @@
+"""library to handle helix commands"""
+import json
+from restkit import Resource
+
+from helixexceptions import HelixException
+from helixexceptions import HelixAlreadyExistsException
+from helixexceptions import HelixDoesNotExistException
+
+
+def _post_payload(host, path, data, **kwargs):
+    """generic function to handle posting data
+    :rtype : return body of page
+    :param host: host to send data to
+    :param path: path to interact with
+    :param data: data to send
+    :param kwargs:  additional keyword args
+    """
+
+    if "http://" not in host:
+        host = "http://{0}".format(host)
+
+    res = Resource(host)
+
+    payload = "jsonParameters={0}".format(json.dumps(data))
+    for key, value in kwargs.items():
+        payload += '&{0}={1}'.format(key, json.dumps(value))
+    headers = {"Content-Type": "application/json"}
+    # print "path is %s" % path
+    page = res.post(path=path, payload=payload, headers=headers)
+    body = page.body_string()
+    if body:
+        body = json.loads(body)
+
+        if isinstance(body, dict) and "ERROR" in body:
+            raise HelixException(body["ERROR"])
+
+    # test what was returned, see if any exceptions need to be raise
+    # if not body:
+    # raise HelixException("body for path {0} is empty".format(path))
+    # else:
+    # print "BODY IS EMPTY FOR ", path
+    # print "BODY is %s." % body
+
+    return body
+
+
+def _get_page(host, path):
+    """if we're specifying a cluster then verify that a cluster is set"""
+
+    if "http://" not in host:
+        host = "http://{0}".format(host)
+
+    res = Resource(host)
+
+    page = res.get(path=path)
+    data = page.body_string()
+    body = None
+    try:
+        body = json.loads(data)
+    except ValueError:
+        body = json.loads(data[:-3])
+
+    # test what was returned, see if any exceptions need to be raise
+    if not body:
+        raise HelixException("body for path {0} is empty".format(path))
+
+    if isinstance(body, dict) and "ERROR" in body:
+        raise HelixException(body["ERROR"])
+
+    return body
+
+
+def _delete_page(host, path):
+    """delete page at a given path"""
+    retval = None
+    if "http://" not in host:
+        host = "http://{0}".format(host)
+
+    res = Resource(host)
+
+    page = res.delete(path)
+    data = page.body_string()
+    if data:
+        retval = json.loads(data)
+
+    return retval
+
+
+def get_clusters(host):
+    """ querys helix cluster for all clusters """
+    return _get_page(host, "/clusters")["listFields"]["clusters"]
+
+
+def get_resource_groups(host, cluster):
+    """ querys helix cluster for resources groups of the current cluster"""
+    return _get_page(host, "/clusters/{0}/resourceGroups".format(cluster))[
+        "listFields"]["ResourceGroups"]
+
+
+def get_resource_tags(host, cluster):
+    """returns a dict of resource tags for a cluster"""
+    return _get_page(host, "/clusters/{0}/resourceGroups".format(cluster))[
+        "mapFields"]["ResourceTags"]
+
+
+def get_resource_group(host, cluster, resource):
+    """ gets the ideal state of the specified resource group of the
+    current cluster"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixException(
+            "{0} is not a resource group of {1}".format(resource, cluster))
+
+    return _get_page(host, "/clusters/{0}/resourceGroups/{1}".format(cluster,
+                                                                     resource))
+
+
+def get_ideal_state(host, cluster, resource):
+    """ gets the ideal state of the specified resource group of the
+    current cluster"""
+
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixException(
+            "{0} is not a resource group of {1}".format(resource, cluster))
+
+    return _get_page(host, "/clusters/{0}/resourceGroups/{1}/idealState".
+                     format(cluster, resource))["mapFields"]
+
+
+def get_external_view(host, cluster, resource):
+    """return the external view for a given cluster and resource"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixException(
+            "{0} is not a resource group of {1}".format(resource, cluster))
+
+    return _get_page(host,
+                     "/clusters/{0}/resourceGroups/{1}/externalView".format(
+                         cluster, resource))["mapFields"]
+
+
+def get_instances(host, cluster):
+    """get list of instances registered to the cluster"""
+    if not cluster:
+        raise HelixException("Cluster must be set before "
+                             "calling this function")
+
+    return _get_page(host, "/clusters/{0}/instances".format(cluster))[
+        "instanceInfo"]
+
+
+def get_instance_detail(host, cluster, name):
+    """get details of an instance"""
+    return _get_page(host, "/clusters/{0}/instances/{1}".format(cluster, name))
+
+
+def get_config(host, cluster, config):
+    """get requested config"""
+    return _get_page(host, "/clusters/{0}/configs/{1}".format(cluster, config))
+
+
+def add_cluster(host, cluster):
+    """add a cluster to helix"""
+    if cluster in get_clusters(host):
+        raise HelixAlreadyExistsException(
+            "Cluster {0} already exists".format(cluster))
+
+    data = {"command": "addCluster",
+            "clusterName": cluster}
+
+    page = _post_payload(host, "/clusters", data)
+    return page
+
+
+def add_instance(host, cluster, instances, port):
+    """add a list of instances to a cluster"""
+    if cluster not in get_clusters(host):
+        raise HelixDoesNotExistException(
+            "Cluster {0} does not exist".format(cluster))
+
+    if not isinstance(instances, list):
+        instances = [instances]
+    instances = ["{0}:{1}".format(instance, port) for instance in instances]
+    try:
+        newinstances = set(instances)
+        oldinstances = set(
+            [x["id"].replace('_', ':') for x in get_instances(host, cluster)])
+        instances = list(newinstances - oldinstances)
+    except HelixException:
+        # this will get thrown if instances is empty,
+        # which if we're just populating should happen
+        pass
+
+    if instances:
+        data = {"command": "addInstance",
+                "instanceNames": ";".join(instances)}
+
+        instance_path = "/clusters/{0}/instances".format(cluster)
+        # print "adding to", instance_path
+        page = _post_payload(host, instance_path, data)
+        return page
+
+    else:
+        raise HelixAlreadyExistsException(
+            "All instances given already exist in cluster")
+
+
+def rebalance(host, cluster, resource, replicas, key=""):
+    """rebalance the given resource group"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixException(
+            "{0} is not a resource group of {1}".format(resource, cluster))
+
+    data = {"command": "rebalance",
+            "replicas": replicas}
+
+    if key:
+        data["key"] = key
+    page = _post_payload(host,
+                         "/clusters/{0}/resourceGroups/{1}/idealState".format(
+                             cluster, resource), data)
+    return page
+
+
+def activate_cluster(host, cluster, grand_cluster, enabled=True):
+    """activate the cluster with the grand cluster"""
+    if grand_cluster not in get_clusters(host):
+        raise HelixException(
+            "grand cluster {0} does not exist".format(grand_cluster))
+
+    data = {'command': 'activateCluster',
+            'grandCluster': grand_cluster}
+
+    if enabled:
+        data["enabled"] = "true"
+    else:
+        data["enabled"] = "false"
+
+    page = _post_payload(host, "/clusters/{0}".format(cluster), data)
+    return page
+
+
+def deactivate_cluster(host, cluster, grand_cluster):
+    """deactivate the cluster with the grand cluster"""
+    return activate_cluster(host, cluster, grand_cluster, enabled=False)
+
+
+def add_resource(host, cluster, resource, partitions,
+                 state_model_def, mode=""):
+    """Add given resource group"""
+    if resource in get_resource_groups(host, cluster):
+        raise HelixAlreadyExistsException(
+            "ResourceGroup {0} already exists".format(resource))
+
+    data = {"command": "addResource",
+            "resourceGroupName": resource,
+            "partitions": partitions,
+            "stateModelDefRef": state_model_def}
+
+    if mode:
+        data["mode"] = mode
+
+    return _post_payload(host, "/clusters/{0}/resourceGroups".format(cluster),
+                         data)
+
+
+def enable_resource(host, cluster, resource, enabled=True):
+    """enable or disable specified resource"""
+    data = {"command": "enableResource"}
+    if enabled:
+        data["enabled"] = "true"
+    else:
+        data["enabled"] = "false"
+
+    return _post_payload(host, "/clusters/{0}/resourceGroups/{1}".format(
+        cluster, resource), data)
+
+
+def disable_resource(host, cluster, resource):
+    """function for disabling resources"""
+    return enable_resource(host, cluster, resource, enabled=False)
+
+
+def alter_ideal_state(host, cluster, resource, newstate):
+    """alter ideal state"""
+    data = {"command": "alterIdealState"}
+    return _post_payload(host,
+                         "/clusters/{0}/resourceGroups/{1}/idealState".format(
+                             cluster, resource), data,
+                         newIdealState=newstate)
+
+
+def enable_instance(host, cluster, instance, enabled=True):
+    """enable instance within cluster"""
+    data = {"command": "enableInstance"}
+    if enabled:
+        data["enabled"] = "true"
+    else:
+        data["enabled"] = "false"
+
+    return _post_payload(host, "/clusters/{0}/instances/{1}".format(cluster,
+                                                                    instance),
+                         data)
+
+
+def disable_instance(host, cluster, instance):
+    """wrapper for ease of use for disabling an instance"""
+    return enable_instance(host, cluster, instance, enabled=False)
+
+
+def swap_instance(host, cluster, old, new):
+    """swap instance"""
+    data = {"command": "swapInstance",
+            "oldInstance": old,
+            "newInstance": new}
+
+    return _post_payload(host, "/cluster/{0}/instances".format(cluster), data)
+
+
+def enable_partition(host, cluster, resource, partition, instance,
+                     enabled=True):
+    """enable Partition """
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixDoesNotExistException(
+            "ResourceGroup {0} does not exist".format(resource))
+
+    data = {"command": "enablePartition",
+            "resource": resource,
+            "partition": partition,
+            "enabled": enabled}
+    return _post_payload(host, "/clusters/{0}/instances/{1}".format(cluster,
+                                                                    instance),
+                         data)
+
+
+def disable_partition(host, cluster, resource, partitions, instance):
+    """disable Partition """
+    return enable_partition(host, cluster, resource, partitions, instance,
+                            enabled=False)
+
+
+def reset_partition(host, cluster, resource, partitions, instance):
+    """reset partition"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixDoesNotExistException(
+            "ResourceGroup {0} does not exist".format(resource))
+
+    data = {"command": "resetPartition",
+            "resource": resource,
+            "partition": " ".join(partitions)}
+    return _post_payload(host, "/clusters/{0}/instances/{1}".format(cluster,
+                                                                    instance),
+                         data)
+
+
+def reset_resource(host, cluster, resource):
+    """reset resource"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixDoesNotExistException(
+            "ResourceGroup {0} does not exist".format(resource))
+
+    data = {"command": "resetResource"}
+    return _post_payload(host,
+                         "/clusters/{0}/resourceGroups/{1}".format(cluster,
+                                                                   resource),
+                         data)
+
+
+def reset_instance(host, cluster, instance):
+    """reset instance"""
+    if instance not in get_instances(host, cluster):
+        raise HelixDoesNotExistException(
+            "Instance {0} does not exist".format(instance))
+
+    data = {"command": "resetInstance"}
+    return _post_payload(host, "/clusters/{0}/instances/{1}".format(cluster,
+                                                                    instance),
+                         data)
+
+
+def add_instance_tag(host, cluster, instance, tag):
+    """add tag to an instance"""
+    data = {"command": "addInstanceTag",
+            "instanceGroupTag": tag}
+    return _post_payload(host,
+                         "/clusters/{0}/instances/{1}".format(
+                             cluster, instance), data)
+
+
+def del_instance_tag(host, cluster, instance, tag):
+    """remove tag from instance"""
+    data = {"command": "removeInstanceTag",
+            "instanceGroupTag": tag}
+    return _post_payload(host,
+                         "/clusters/{0}/instances/{1}".format(
+                             cluster, instance), data)
+
+
+def add_resource_tag(host, cluster, resource, tag):
+    """add tag to resource group"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixDoesNotExistException(
+            "ResourceGroup {0} does not exist".format(resource))
+
+    data = {"command": "addResourceProperty",
+            "INSTANCE_GROUP_TAG": tag}
+    return _post_payload(host,
+                         "/clusters/{0}/resourceGroups/{1}/idealState".format(
+                             cluster, resource), data)
+
+
+"""
+del resource currently does not exist in helix api
+def del_resource_tag(host, cluster, resource, tag):
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixDoesNotExistException(
+            "ResourceGroup {0} does not exist".format(resource))
+
+    data = {"command": "removeResourceProperty",
+            "INSTANCE_GROUP_TAG": tag}
+    return _post_payload(host,
+                         "/clusters/{0}/resourceGroups/{1}/idealState".format(
+                             cluster, resource), data)
+"""
+
+
+def get_instance_taginfo(host, cluster):
+    return _get_page(host, "/clusters/{0}/instances".format(
+        cluster))["tagInfo"]
+
+
+def expand_cluster(host, cluster):
+    """expand cluster"""
+    data = {"command": "expandCluster"}
+
+    return _post_payload(host, "/clusters/{0}/".format(cluster), data)
+
+
+def expand_resource(host, cluster, resource):
+    """expand resource"""
+    data = {"command": "expandResource"}
+
+    return _post_payload(host,
+                         "/clusters/{0}/resourceGroup/{1}/idealState".format(
+                             cluster, resource), data)
+
+
+def add_resource_property(host, cluster, resource, properties):
+    """add resource property properties must be a dictionary of properties"""
+    properties["command"] = "addResourceProperty"
+
+    return _post_payload(host,
+                         "/clusters/{0}/resourceGroup/{1}/idealState".format(
+                             cluster, resource), properties)
+
+
+def _handle_config(host, cluster, configs, command, participant=None,
+                   resource=None):
+    """helper function to set or delete configs in helix"""
+    data = {"command": "{0}Config".format(command),
+            "configs": ",".join(
+                ["{0}={1}".format(x, y) for x, y in configs.items()])}
+
+    address = "/clusters/{0}/configs/".format(cluster)
+    if participant:
+        address += "participant/{0}".format(participant)
+    elif resource:
+        address += "resource/{0}".format(resource)
+    else:
+        address += "cluster"
+
+    return _post_payload(host, address, data)
+
+
+def set_config(host, cluster, configs, participant=None, resource=None):
+    """sets config in helix"""
+    return _handle_config(host, cluster, configs, "set", participant, resource)
+
+
+def remove_config(host, cluster, configs, participant=None, resource=None):
+    """sets config in helix"""
+    return _handle_config(host, "remove", cluster, configs, participant,
+                          resource)
+
+
+def get_zk_path(host, path):
+    """get zookeeper path"""
+    return _get_page(host, "zkPath/{0}".format(path))
+
+
+def del_zk_path(host, path):
+    """delete zookeeper path"""
+    return _delete_page(host, "zkPath/{0}".format(path))
+
+
+def get_zk_child(host, path):
+    """get zookeeper child"""
+    return _get_page(host, "zkChild/{0}".format(path))
+
+
+def del_zk_child(host, path):
+    """delete zookeeper child"""
+    return _delete_page(host, "zkChild/{0}".format(path))
+
+
+def add_state_model(host, cluster, newstate):
+    """add state model"""
+    data = {"command": "addStateModel"}
+
+    return _post_payload(host, "/clusters/{0}/StateModelDefs".format(cluster),
+                         data, newStateModelDef=newstate)
+
+
+def del_instance(host, cluster, instance):
+    """delete instance"""
+    if instance not in [x["id"] for x in get_instances(host, cluster)]:
+        raise HelixDoesNotExistException(
+            "Instance {0} does not exist.".format(instance))
+
+    page = _delete_page(host,
+                        "/clusters/{0}/instances/{1}".format(cluster,
+                                                             instance))
+    return page
+
+
+def del_resource(host, cluster, resource):
+    """delete specified resource from cluster"""
+    if resource not in get_resource_groups(host, cluster):
+        raise HelixDoesNotExistException(
+            "ResourceGroup {0} does not exist".format(resource))
+
+    page = _delete_page(host, "/clusters/{0}/resourceGroups/{1}".format(
+        cluster, resource))
+    return page
+
+
+def del_cluster(host, cluster):
+    """delete cluster"""
+    page = _delete_page(host, "/clusters/{0}".format(cluster))
+
+    return page
+
+
+def send_message(host, cluster, path, **kwargs):
+    pass

http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/helixexceptions.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/helixexceptions.py b/contributors/py-helix-admin/helix/helixexceptions.py
new file mode 100644
index 0000000..63ffec1
--- /dev/null
+++ b/contributors/py-helix-admin/helix/helixexceptions.py
@@ -0,0 +1,16 @@
+"""library to handle helix exceptions"""
+
+
+class HelixException(Exception):
+    """Base helix exception"""
+    pass
+
+
+class HelixAlreadyExistsException(HelixException):
+    """Exception is thrown when an entry in helix already exists"""
+    pass
+
+
+class HelixDoesNotExistException(HelixException):
+    """Exception is thrown when an entry in helix does not exist"""
+    pass

http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/participant.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/participant.py b/contributors/py-helix-admin/helix/participant.py
new file mode 100644
index 0000000..678f93b
--- /dev/null
+++ b/contributors/py-helix-admin/helix/participant.py
@@ -0,0 +1,66 @@
+"""base class for anything that connects to helix"""
+from helixexceptions import HelixException
+
+
+class Participant(object):
+    """Basic model for a helix participant"""
+
+    def __init__(self, ident, alive, enabled, data):
+        super(Participant, self).__init__()
+        self.ident = ident
+        self.hostname, self.port = ident.split("_")
+        self.partitions = {}
+        self.data = data
+        self.enabled = None
+        self._tags = []
+        self._disabled_partitions = []
+
+        if isinstance(enabled, str) or isinstance(enabled, unicode):
+            if enabled == "true":
+                self.enabled = True
+            else:
+                self.enabled = False
+        elif isinstance(enabled, bool):
+            self.enabled = enabled
+
+        self.alive = bool(alive)
+        self.update()
+
+    def __repr__(self):
+        return "{0}('{1}', {2}, {3}, {4})".format(self.__class__.__name__,
+                                                  self.ident, self.alive,
+                                                  self.enabled, self.data)
+
+    def __str__(self):
+        return "Id: {0} Enabled: {1} Alive: {2}".format(self.ident,
+                                                        self.enabled,
+                                                        self.alive)
+
+    def update(self, data=None):
+        """update data for participant then update values"""
+        if data:
+            self.data = data
+
+        if "TAG_LIST" in self.data["listFields"]:
+            self._tags = self.data["listFields"]["TAG_LIST"]
+
+        if "HELIX_DISABLED_PARTITION" in self.data["listFields"]:
+            self._disabled_partitions = \
+                self.data["listFields"]["HELIX_DISABLED_PARTITION"]
+
+    @property
+    def tags(self):
+        return self._tags
+
+    @tags.setter
+    def tags(self, value):
+        """ensure an exception is raise on an attempt to set tags this way"""
+        raise HelixException("Tags must be set on a cluster object")
+
+    @property
+    def disabled_partitions(self):
+        return self._disabled_partitions
+
+    @disabled_partitions.setter
+    def disabled_partitions(self, value):
+        raise HelixException("Partitions must be disabled on a cluster object")

http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/partition.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/partition.py b/contributors/py-helix-admin/helix/partition.py
new file mode 100644
index 0000000..e028567
--- /dev/null
+++ b/contributors/py-helix-admin/helix/partition.py
@@ -0,0 +1,18 @@
+"""base class for anything that connects to helix"""
+
+
+class Partition(object):
+    """Object to deal helix partitions"""
+
+    def __init__(self, name, hosts):
+        super(Partition, self).__init__()
+        self.name = name
+        self.hosts = hosts
+
+    def __str__(self):
+        return "Partition {0} - Hosts: {1}".format(self.name, ", ".join(
+            [x.ident for x in self.hosts]))
+
+    def __repr__(self):
+        return "{0}('{1}', {2})".format(self.__class__.__name__, self.name,
+                                        self.hosts)

http://git-wip-us.apache.org/repos/asf/helix/blob/e2a033f8/contributors/py-helix-admin/helix/resourcegroup.py
----------------------------------------------------------------------
diff --git a/contributors/py-helix-admin/helix/resourcegroup.py b/contributors/py-helix-admin/helix/resourcegroup.py
new file mode 100644
index 0000000..62c447f
--- /dev/null
+++ b/contributors/py-helix-admin/helix/resourcegroup.py
@@ -0,0 +1,34 @@
+"""base class for anything that connects to helix"""
+
+import partition
+from helixexceptions import HelixException
+
+
+class ResourceGroup(object):
+    """Object to deal with resource groups"""
+
+    def __init__(self, name, count, replicas, statemode, data):
+        super(ResourceGroup, self).__init__()
+        self.name = name
+        self.count = count
+        self.replicas = replicas
+        self.state_model_def_ref = statemode
+        self.data = data
+        self.partitions = {}
+
+    def __str__(self):
+        return "Resource: {0} - Count: {1}".format(self.name, self.count)
+
+    def __repr__(self):
+        return "{0}('{1}', {2}, {3}, {4}, {5})".format(self.__class__.__name__,
+                                                       self.name, self.count,
+                                                       self.replicas,
+                                                       self.
+                                                       state_model_def_ref,
+                                                       self.data)
+
+    def add_partition(self, part):
+        """add a partition to this resource group"""
+        if not isinstance(part, partition.Partition):
+            raise HelixException("Argument part must be Partition or subclass")
+        self.partitions[part.name] = part


Mime
View raw message