Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 1 | import os |
Scott Baker | 714d524 | 2015-08-05 08:20:12 -0700 | [diff] [blame] | 2 | import pdb |
Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 3 | import json |
Scott Baker | f65591f | 2015-09-28 16:01:21 -0700 | [diff] [blame] | 4 | import subprocess |
Scott Baker | ccf785f | 2016-05-03 15:55:16 -0700 | [diff] [blame] | 5 | import sys |
Scott Baker | 714d524 | 2015-08-05 08:20:12 -0700 | [diff] [blame] | 6 | |
Scott Baker | 07c71fc | 2015-09-24 15:37:16 -0700 | [diff] [blame] | 7 | from core.models import User |
| 8 | |
Scott Baker | 3841b37 | 2015-08-03 14:20:31 -0700 | [diff] [blame] | 9 | class XOSResource(object): |
| 10 | xos_base_class = "XOSResource" |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 11 | xos_model = None |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 12 | name_field = "name" |
| 13 | copyin_props = [] |
Scott Baker | 3841b37 | 2015-08-03 14:20:31 -0700 | [diff] [blame] | 14 | provides = None |
| 15 | |
Scott Baker | 5d43214 | 2015-08-07 17:06:47 -0700 | [diff] [blame] | 16 | def __init__(self, user, nodetemplate, engine): |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 17 | self.dirty = False |
Scott Baker | 404c46d | 2015-08-24 15:50:03 -0700 | [diff] [blame] | 18 | self.deferred_sync = [] |
Scott Baker | 3841b37 | 2015-08-03 14:20:31 -0700 | [diff] [blame] | 19 | self.user = user |
| 20 | self.nodetemplate = nodetemplate |
Scott Baker | 5d43214 | 2015-08-07 17:06:47 -0700 | [diff] [blame] | 21 | self.engine = engine |
Scott Baker | 3841b37 | 2015-08-03 14:20:31 -0700 | [diff] [blame] | 22 | |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 23 | @property |
| 24 | def full_name(self): |
| 25 | return self.nodetemplate.name |
| 26 | |
| 27 | @property |
| 28 | def obj_name(self): |
| 29 | if "#" in self.nodetemplate.name: |
Scott Baker | 3826522 | 2016-04-25 15:19:03 -0700 | [diff] [blame] | 30 | return self.nodetemplate.name.split("#",1)[1] |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 31 | else: |
| 32 | return self.nodetemplate.name |
| 33 | |
Scott Baker | 3279525 | 2015-08-04 23:53:07 -0700 | [diff] [blame] | 34 | def get_all_required_node_names(self): |
| 35 | results = [] |
| 36 | for reqs in self.nodetemplate.requirements: |
| 37 | for (k,v) in reqs.items(): |
| 38 | results.append(v["node"]) |
| 39 | return results |
| 40 | |
Scott Baker | 1415294 | 2015-08-04 10:59:29 -0700 | [diff] [blame] | 41 | def get_requirements(self, relationship_name, throw_exception=False): |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 42 | """ helper to search the list of requirements for a particular relationship |
| 43 | type. |
| 44 | """ |
Scott Baker | 1415294 | 2015-08-04 10:59:29 -0700 | [diff] [blame] | 45 | |
| 46 | results = [] |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 47 | for reqs in self.nodetemplate.requirements: |
| 48 | for (k,v) in reqs.items(): |
| 49 | if (v["relationship"] == relationship_name): |
Scott Baker | 1415294 | 2015-08-04 10:59:29 -0700 | [diff] [blame] | 50 | results.append(v["node"]) |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 51 | |
Scott Baker | 1415294 | 2015-08-04 10:59:29 -0700 | [diff] [blame] | 52 | if (not results) and throw_exception: |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 53 | raise Exception("Failed to find requirement in %s using relationship %s" % (self.full_name, relationship_name)) |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 54 | |
Scott Baker | 1415294 | 2015-08-04 10:59:29 -0700 | [diff] [blame] | 55 | return results |
| 56 | |
| 57 | def get_requirement(self, relationship_name, throw_exception=False): |
| 58 | reqs = self.get_requirements(relationship_name, throw_exception) |
| 59 | if not reqs: |
| 60 | return None |
| 61 | return reqs[0] |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 62 | |
Scott Baker | 714d524 | 2015-08-05 08:20:12 -0700 | [diff] [blame] | 63 | def get_scalable(self): |
Scott Baker | 502400a | 2015-08-05 10:41:51 -0700 | [diff] [blame] | 64 | scalable = self.nodetemplate.get_capabilities().get("scalable", None) |
| 65 | if scalable: |
| 66 | return {"min_instances": scalable.get_property_value("min_instances"), |
| 67 | "max_instances": scalable.get_property_value("max_instances"), |
| 68 | "default_instances": scalable.get_property_value("default_instances")} |
Scott Baker | 714d524 | 2015-08-05 08:20:12 -0700 | [diff] [blame] | 69 | else: |
| 70 | return {} |
| 71 | |
Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 72 | def get_property(self, name): |
| 73 | return self.nodetemplate.get_property_value(name) |
Scott Baker | 068b118 | 2015-08-05 18:34:23 -0700 | [diff] [blame] | 74 | |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 75 | def get_property_default(self, name, default=None): |
| 76 | props = self.nodetemplate.get_properties() |
Sapan Bhatia | 16be143 | 2016-01-14 11:41:38 -0500 | [diff] [blame] | 77 | if props and name in props.keys(): |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 78 | return props[name].value |
| 79 | return default |
| 80 | |
Scott Baker | 5fcce04 | 2015-08-24 16:25:46 -0700 | [diff] [blame] | 81 | def get_xos_object(self, cls, throw_exception=True, **kwargs): |
Scott Baker | 3dd46f8 | 2016-04-28 08:41:31 -0700 | [diff] [blame] | 82 | # do the same parsing that we do for objname |
| 83 | for (k,v) in kwargs.items(): |
| 84 | if (k=="name") and ("#" in v): |
| 85 | kwargs[k] = v.split("#",1)[1] |
| 86 | |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 87 | objs = cls.objects.filter(**kwargs) |
| 88 | if not objs: |
Scott Baker | 5fcce04 | 2015-08-24 16:25:46 -0700 | [diff] [blame] | 89 | if throw_exception: |
| 90 | raise Exception("Failed to find %s filtered by %s" % (cls.__name__, str(kwargs))) |
| 91 | return None |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 92 | return objs[0] |
| 93 | |
Scott Baker | ccf785f | 2016-05-03 15:55:16 -0700 | [diff] [blame] | 94 | def get_replaces_objs(self): |
| 95 | replaces = self.get_property_default("replaces", None) |
| 96 | if replaces: |
| 97 | return self.xos_model.objects.filter(**{self.name_field: replaces}) |
| 98 | else: |
| 99 | return [] |
| 100 | |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 101 | def get_existing_objs(self): |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 102 | return self.xos_model.objects.filter(**{self.name_field: self.obj_name}) |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 103 | |
Scott Baker | c058150 | 2015-09-01 22:57:46 -0700 | [diff] [blame] | 104 | def get_model_class_name(self): |
| 105 | return self.xos_model.__name__ |
| 106 | |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 107 | def create_or_update(self): |
Scott Baker | ccf785f | 2016-05-03 15:55:16 -0700 | [diff] [blame] | 108 | replaces_objs = self.get_replaces_objs() |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 109 | existing_objs = self.get_existing_objs() |
Scott Baker | ccf785f | 2016-05-03 15:55:16 -0700 | [diff] [blame] | 110 | |
| 111 | if (replaces_objs and existing_objs): |
| 112 | ro = replaces_objs[0] |
| 113 | self.info("deleting %s:%s" % (self.get_model_class_name(), getattr(ro,self.name_field))) |
| 114 | ro.delete() |
| 115 | |
| 116 | # in case we wanted to throw an error instead... |
| 117 | #self.error("CRITICAL ERROR: Both %s and %s exist!" % (getattr(ro,self.name_field), self.obj_name)) |
| 118 | #sys.exit(-1) |
| 119 | |
| 120 | if (replaces_objs and not existing_objs): |
| 121 | ro = replaces_objs[0] |
| 122 | self.info("renaming %s:%s to %s" % (self.get_model_class_name(), getattr(ro,self.name_field), self.obj_name)) |
| 123 | setattr(ro, self.name_field, self.obj_name) |
| 124 | ro.save() |
| 125 | existing_objs = self.get_existing_objs() |
| 126 | |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 127 | if existing_objs: |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 128 | if self.get_property_default("no-update", False): |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 129 | self.info("%s:%s (%s) already exists. Skipping update due to 'no-update' property" % (self.get_model_class_name(), self.obj_name, self.full_name)) |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 130 | else: |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 131 | self.info("%s:%s (%s) already exists" % (self.get_model_class_name(), self.obj_name, self.full_name)) |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 132 | self.update(existing_objs[0]) |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 133 | else: |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 134 | if self.get_property_default("no-create", False): |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 135 | self.info("%s:%s (%s) does not exist, but 'no-create' is specified" % (self.get_model_class_name(), self.obj_name, self.full_name)) |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 136 | else: |
| 137 | self.create() |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 138 | |
Scott Baker | 61c4311 | 2015-08-12 19:06:16 -0700 | [diff] [blame] | 139 | def can_delete(self, obj): |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 140 | if self.get_property_default("no-delete",False): |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 141 | self.info("%s:%s %s is marked 'no-delete'. Skipping delete." % (self.get_model_class_name(), self.obj_name, self.full_name)) |
Scott Baker | 3314287 | 2015-10-08 16:51:34 -0700 | [diff] [blame] | 142 | return False |
Scott Baker | 61c4311 | 2015-08-12 19:06:16 -0700 | [diff] [blame] | 143 | return True |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 144 | |
Scott Baker | 07c71fc | 2015-09-24 15:37:16 -0700 | [diff] [blame] | 145 | def postprocess_privileges(self, roleclass, privclass, rolemap, obj, toFieldName): |
Scott Baker | 92362bc | 2015-09-24 15:22:46 -0700 | [diff] [blame] | 146 | for (rel, role) in rolemap: |
| 147 | for email in self.get_requirements(rel): |
Scott Baker | 688c4dd | 2016-01-27 19:00:06 -0800 | [diff] [blame] | 148 | role_obj = self.get_xos_object(roleclass, throw_exception=False, role=role) |
| 149 | if not role_obj: |
| 150 | # if the role doesn't exist, make it |
| 151 | self.info("Creating %s %s" % (roleclass.__name__, role)) |
| 152 | role_obj = roleclass(role=role) |
| 153 | role_obj.save() |
| 154 | |
Scott Baker | 92362bc | 2015-09-24 15:22:46 -0700 | [diff] [blame] | 155 | user = self.get_xos_object(User, email=email) |
Scott Baker | 688c4dd | 2016-01-27 19:00:06 -0800 | [diff] [blame] | 156 | if not privclass.objects.filter(user=user, role=role_obj, **{toFieldName: obj}): |
| 157 | sp = privclass(user=user, role=role_obj, **{toFieldName: obj}) |
Scott Baker | 92362bc | 2015-09-24 15:22:46 -0700 | [diff] [blame] | 158 | sp.save() |
| 159 | self.info("Added privilege on %s role %s for %s" % (str(obj), str(role), str(user))) |
| 160 | |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 161 | def postprocess(self, obj): |
| 162 | pass |
| 163 | |
Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 164 | def intrinsic_get_artifact(self, obj=None, name=None, method=None): |
| 165 | if obj!="SELF": |
| 166 | raise Exception("only SELF is supported for get_artifact first arg") |
| 167 | if method!="LOCAL_FILE": |
| 168 | raise Exception("only LOCAL_FILE is supported for get_artifact third arg") |
| 169 | |
| 170 | for (k,v) in self.nodetemplate.entity_tpl.get("artifacts", {}).items(): |
| 171 | if k == name: |
| 172 | if not os.path.exists(v): |
| 173 | raise Exception("Artifact local file %s for artifact %s does not exist" % (v, k)) |
| 174 | return open(v).read() |
| 175 | |
| 176 | raise Exception("artifact %s not found" % name) |
| 177 | |
Scott Baker | f65591f | 2015-09-28 16:01:21 -0700 | [diff] [blame] | 178 | def intrinsic_get_script_env(self, obj=None, name=None, varname=None, method=None): |
| 179 | if obj!="SELF": |
| 180 | raise Exception("only SELF is supported for get_artifact first arg") |
| 181 | if method!="LOCAL_FILE": |
| 182 | raise Exception("only LOCAL_FILE is supported for get_artifact fourth arg") |
| 183 | |
| 184 | for (k,v) in self.nodetemplate.entity_tpl.get("artifacts", {}).items(): |
| 185 | if k == name: |
| 186 | if not os.path.exists(v): |
| 187 | raise Exception("Artifact local file %s for artifact %s does not exist" % (v, k)) |
| 188 | return subprocess.Popen('/bin/bash -c "source %s &> /dev/null; echo \\$%s"' % (v, varname), shell=True, stdout=subprocess.PIPE).stdout.read().strip() |
| 189 | |
| 190 | raise Exception("artifact %s not found" % name) |
| 191 | |
Scott Baker | 18cd462 | 2016-06-03 15:49:16 -0700 | [diff] [blame] | 192 | def intrinsic_path_join(self, obj=None, name=None, varname=None, method=None): |
| 193 | if obj!="SELF": |
| 194 | raise Exception("only SELF is supported for get_artifact first arg") |
| 195 | if method!="ENV_VAR": |
| 196 | raise Exception("only ENV_VAR is supported for get_artifact fourth arg") |
| 197 | |
| 198 | if not (name in os.environ): |
| 199 | raise Exception("environment variable %s not found" % name) |
| 200 | |
| 201 | return os.path.join(os.environ[name], varname) |
| 202 | |
Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 203 | def try_intrinsic_function(self, v): |
| 204 | try: |
| 205 | jsv = v.replace("'", '"') |
| 206 | jsv = json.loads(jsv) |
| 207 | except: |
| 208 | #import traceback |
| 209 | #traceback.print_exc() |
| 210 | return v |
| 211 | |
| 212 | if type(jsv)!=dict: |
| 213 | return v |
| 214 | |
| 215 | if "get_artifact" in jsv: |
| 216 | return self.intrinsic_get_artifact(*jsv["get_artifact"]) |
Scott Baker | f65591f | 2015-09-28 16:01:21 -0700 | [diff] [blame] | 217 | elif "get_script_env" in jsv: |
| 218 | return self.intrinsic_get_script_env(*jsv["get_script_env"]) |
Scott Baker | 18cd462 | 2016-06-03 15:49:16 -0700 | [diff] [blame] | 219 | elif "path_join" in jsv: |
| 220 | return self.intrinsic_path_join(*jsv["path_join"]) |
Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 221 | |
| 222 | return v |
| 223 | |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 224 | def get_xos_args(self): |
| 225 | args = {} |
| 226 | |
| 227 | if self.name_field: |
Scott Baker | 642dbb5 | 2016-04-25 14:55:22 -0700 | [diff] [blame] | 228 | args[self.name_field] = self.obj_name |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 229 | |
| 230 | # copy simple string properties from the template into the arguments |
| 231 | for prop in self.copyin_props: |
| 232 | v = self.get_property(prop) |
Scott Baker | 35a9953 | 2015-08-31 16:18:08 -0700 | [diff] [blame] | 233 | |
| 234 | v = self.try_intrinsic_function(v) |
| 235 | |
Scott Baker | 744f9a1 | 2015-09-02 16:29:16 -0700 | [diff] [blame] | 236 | if v is not None: |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 237 | args[prop] = v |
| 238 | |
| 239 | return args |
| 240 | |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 241 | def create(self): |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 242 | xos_args = self.get_xos_args() |
| 243 | xos_obj = self.xos_model(**xos_args) |
Scott Baker | 4ce7d4f | 2016-04-22 09:26:18 -0700 | [diff] [blame] | 244 | if self.user: |
| 245 | xos_obj.caller = self.user |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 246 | xos_obj.save() |
| 247 | |
Scott Baker | 2142bd7 | 2015-08-10 17:08:02 -0700 | [diff] [blame] | 248 | self.info("Created %s '%s'" % (self.xos_model.__name__,str(xos_obj))) |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 249 | |
Scott Baker | 3793772 | 2015-08-14 12:41:18 -0700 | [diff] [blame] | 250 | self.postprocess(xos_obj) |
| 251 | |
Scott Baker | 9fdb39f | 2015-08-04 16:44:18 -0700 | [diff] [blame] | 252 | def update(self, obj): |
Scott Baker | 744f9a1 | 2015-09-02 16:29:16 -0700 | [diff] [blame] | 253 | xos_args = self.get_xos_args() |
Scott Baker | 41916c9 | 2015-09-03 12:15:50 -0700 | [diff] [blame] | 254 | for (k,v) in xos_args.items(): |
| 255 | setattr(obj, k, v) |
Scott Baker | eb2aa3c | 2015-09-03 14:52:22 -0700 | [diff] [blame] | 256 | self.postprocess(obj) |
Scott Baker | 744f9a1 | 2015-09-02 16:29:16 -0700 | [diff] [blame] | 257 | obj.save() |
Scott Baker | 3841b37 | 2015-08-03 14:20:31 -0700 | [diff] [blame] | 258 | |
Scott Baker | 13399c0 | 2015-08-05 16:35:09 -0700 | [diff] [blame] | 259 | def delete(self, obj): |
Scott Baker | 61c4311 | 2015-08-12 19:06:16 -0700 | [diff] [blame] | 260 | if (self.can_delete(obj)): |
| 261 | self.info("destroying object %s" % str(obj)) |
| 262 | obj.delete(purge=True) # XXX TODO: turn off purge before production |
Scott Baker | 13399c0 | 2015-08-05 16:35:09 -0700 | [diff] [blame] | 263 | |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 264 | def info(self, s): |
Scott Baker | e08b456 | 2015-08-11 17:23:52 -0700 | [diff] [blame] | 265 | self.engine.log(s) |
Scott Baker | 9fce62e | 2015-08-03 15:43:54 -0700 | [diff] [blame] | 266 | |