You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by ka...@apache.org on 2014/12/08 04:02:53 UTC
[2/4] helix git commit: adding python helix admin library
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 <jh...@jheise-ld1.linkedin.biz>
Authored: Thu Dec 4 15:38:20 2014 -0800
Committer: Jon Heise <jh...@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