Revert "Remove vendored libjuju"
[osm/N2VC.git] / modules / libjuju / juju / constraints.py
diff --git a/modules/libjuju/juju/constraints.py b/modules/libjuju/juju/constraints.py
new file mode 100644 (file)
index 0000000..0050673
--- /dev/null
@@ -0,0 +1,87 @@
+#
+# Module that parses constraints
+#
+# The current version of juju core expects the client to take
+# constraints given in the form "mem=10G foo=bar" and parse them into
+# json that looks like {"mem": 10240, "foo": "bar"}. This module helps us
+# accomplish that task.
+#
+# We do not attempt to duplicate the checking done in
+# client/_client.py:Value here. That class will verify that the
+# constraints keys are valid, and that we can successfully dump the
+# constraints dict to json.
+#
+# Once https://bugs.launchpad.net/juju/+bug/1645402 is addressed, this
+# module should be deprecated.
+#
+
+import re
+
+# Matches on a string specifying memory size
+MEM = re.compile('^[1-9][0-9]*[MGTP]$')
+
+# Multiplication factors to get Megabytes
+# https://github.com/juju/juju/blob/master/constraints/constraints.go#L666
+FACTORS = {
+    "M": 1,
+    "G": 1024,
+    "T": 1024 * 1024,
+    "P": 1024 * 1024 * 1024
+}
+
+LIST_KEYS = {'tags', 'spaces'}
+
+SNAKE1 = re.compile(r'(.)([A-Z][a-z]+)')
+SNAKE2 = re.compile('([a-z0-9])([A-Z])')
+
+
+def parse(constraints):
+    """
+    Constraints must be expressed as a string containing only spaces
+    and key value pairs joined by an '='.
+
+    """
+    if not constraints:
+        return None
+
+    if type(constraints) is dict:
+        # Fowards compatibilty: already parsed
+        return constraints
+
+    constraints = {
+        normalize_key(k): (
+            normalize_list_value(v) if k in LIST_KEYS else
+            normalize_value(v)
+        ) for k, v in [s.split("=") for s in constraints.split(" ")]}
+
+    return constraints
+
+
+def normalize_key(key):
+    key = key.strip()
+
+    key = key.replace("-", "_")  # Our _client lib wants "_" in place of "-"
+
+    # Convert camelCase to snake_case
+    key = SNAKE1.sub(r'\1_\2', key)
+    key = SNAKE2.sub(r'\1_\2', key).lower()
+
+    return key
+
+
+def normalize_value(value):
+    value = value.strip()
+
+    if MEM.match(value):
+        # Translate aliases to Megabytes. e.g. 1G = 10240
+        return int(value[:-1]) * FACTORS[value[-1:]]
+
+    if value.isdigit():
+        return int(value)
+
+    return value
+
+
+def normalize_list_value(value):
+    values = value.strip().split(',')
+    return [normalize_value(value) for value in values]