def handle_request(self, request_handler): foreman = PluginRegistry.getInstance("Foreman") data = loads(request_handler.request.body) self.log.debug(data) # TODO disable hook logging to file with open("foreman-log.json", "a") as f: f.write("%s,\n" % dumps(data, indent=4, sort_keys=True)) if data["event"] in ForemanHookReceiver.skip_next_event and data["object"] in ForemanHookReceiver.skip_next_event[data["event"]]: ForemanHookReceiver.skip_next_event[data["event"]].remove(data["object"]) self.log.info("skipped '%s' event for object: '%s'" % (data["event"], data["object"])) return data_keys = list(data['data'].keys()) if len(data_keys) == 1: type = data_keys[0] else: # no type given -> skipping this event as other might come with more information self.log.warning("skipping event '%s' for object '%s' as no type information is given in data: '%s'" % (data["event"], data["object"], data["data"])) return # search for real data if len(data['data'][type].keys()) == 1: # something like {data: 'host': {host: {...}}} # or {data: 'discovered_host': {host: {...}}} payload_data = data['data'][type][list(data['data'][type].keys())[0]] else: payload_data = data['data'][type] if type == "operatingsystem": with make_session() as session: foreman.sync_release_name(payload_data, session, event=data['event']) session.commit() return factory = ObjectFactory.getInstance() foreman_type = type if type == "discovered_host": type = "host" object_types = factory.getObjectNamesWithBackendSetting("Foreman", "type", "%ss" % type) object_type = object_types[0] if len(object_types) else None backend_attributes = factory.getObjectBackendProperties(object_type) if object_type is not None else None self.log.debug("Hookevent: '%s' for '%s' (%s)" % (data['event'], data['object'], object_type)) uuid_attribute = None if "Foreman" in backend_attributes: uuid_attribute = backend_attributes["Foreman"]["_uuidSourceAttribute"] \ if '_uuidSourceAttribute' in backend_attributes["Foreman"] else backend_attributes["Foreman"]["_uuidAttribute"] ForemanBackend.modifier = "foreman" update_data = {} if data['event'] in ["update", "create"] and foreman_type == "host": id = payload_data["id"] if "id" in payload_data else None try: foreman.write_parameters(id if id is not None else data['object']) except: foreman.mark_for_parameter_setting(data['object'], { "status": "created", "use_id": id }) if data['event'] == "after_commit" or data['event'] == "update" or data['event'] == "after_create" or data['event'] == "create": host = None if data['event'] == "update" and foreman_type == "host" and "mac" in payload_data and payload_data["mac"] is not None: # check if we have an discovered host for this mac index = PluginRegistry.getInstance("ObjectIndex") res = index.search({ "_type": "Device", "extension": ["ForemanHost", "ieee802Device"], "macAddress": payload_data["mac"], "status": "discovered" }, {"dn": 1}) if len(res): self.log.debug("update received for existing host with dn: %s" % res[0]["dn"]) host = ObjectProxy(res[0]["dn"]) if foreman_type != "discovered_host" and host.is_extended_by("ForemanHost"): host.status = "unknown" foreman_object = foreman.get_object(object_type, payload_data[uuid_attribute], create=host is None) if foreman_object and host: if foreman_object != host: self.log.debug("using known host instead of creating a new one") # host is the formerly discovered host, which might have been changed in GOsa for provisioning # so we want to use this one, foreman_object is the joined one, so copy the credentials from foreman_object to host if not host.is_extended_by("RegisteredDevice"): host.extend("RegisteredDevice") if not host.is_extended_by("simpleSecurityObject"): host.extend("simpleSecurityObject") host.deviceUUID = foreman_object.deviceUUID host.userPassword = foreman_object.userPassword host.otp = foreman_object.otp host.cn = foreman_object.cn # now delete the formerly joined host foreman_object.remove() foreman_object = host elif foreman_object is None and host is not None: foreman_object = host elif foreman_type == "discovered_host": self.log.debug("setting discovered state for %s" % payload_data[uuid_attribute]) if not foreman_object.is_extended_by("ForemanHost"): foreman_object.extend("ForemanHost") foreman_object.status = "discovered" if foreman_type == "host": old_build_state = foreman_object.build foreman.update_type(object_type, foreman_object, payload_data, uuid_attribute, update_data=update_data) if foreman_type == "host" and old_build_state is True and foreman_object.build is False and \ foreman_object.status == "ready": # send notification e = EventMaker() ev = e.Event(e.Notification( e.Title(N_("Host ready")), e.Body(N_("Host '%s' has been successfully build." % foreman_object.cn)), e.Icon("@Ligature/pc"), e.Timeout("10000") )) event_object = objectify.fromstring(etree.tostring(ev, pretty_print=True).decode('utf-8')) SseHandler.notify(event_object) elif data['event'] == "after_destroy": # print("Payload: %s" % payload_data) foreman.remove_type(object_type, payload_data[uuid_attribute]) # because foreman sends the after_commit event after the after_destroy event # we need to skip this event, otherwise the host would be re-created if "after_commit" not in ForemanHookReceiver.skip_next_event: ForemanHookReceiver.skip_next_event["after_commit"] = [data['object']] else: ForemanHookReceiver.skip_next_event["after_commit"].append(data['object']) # add garbage collection for skip sobj = PluginRegistry.getInstance("SchedulerService") sobj.getScheduler().add_date_job(self.cleanup_event_skipper, datetime.datetime.now() + datetime.timedelta(minutes=1), args=("after_commit", data['object']), tag='_internal', jobstore='ram') else: self.log.info("unhandled hook event '%s' received for '%s'" % (data['event'], type)) ForemanBackend.modifier = None
async def test_provision_host(self, m_get, m_del, m_put, m_post): """ convert a discovered host to a 'real' host """ self._test_dn = GosaTestCase.create_test_data() container = ObjectProxy(self._test_dn, "IncomingDeviceContainer") container.commit() mocked_foreman = MockForeman() m_get.side_effect = mocked_foreman.get m_del.side_effect = mocked_foreman.delete m_put.side_effect = mocked_foreman.put m_post.side_effect = mocked_foreman.post # create the discovered host + foremanHostgroup d_host = ObjectProxy(container.dn, "Device") d_host.cn = "mac00262df16a2c" d_host.extend("ForemanHost") d_host.status = "discovered" d_host.extend("ieee802Device") d_host.macAddress = "00:26:2d:f1:6a:2c" d_host.extend("IpHost") d_host.ipHostNumber = "192.168.0.1" d_host.commit() hostgroup = ObjectProxy("%s" % self._test_dn, "GroupOfNames") hostgroup.extend("ForemanHostGroup") hostgroup.cn = "Test" hostgroup.foremanGroupId = "4" hostgroup.commit() # add host to group logging.getLogger("test.foreman-integration").info("########### START: Add Host to group ############# %s" % AsyncHTTPTestCase.get_url(self, "/hooks/")) d_host = ObjectProxy("cn=mac00262df16a2c,%s" % container.dn) def check(): logging.getLogger("test.foreman-integration").info("check condition: %s, %s" % (d_host.cn, d_host.status)) return d_host.cn == "mac00262df16a2c" and d_host.status == "discovered" def check2(): logging.getLogger("test.foreman-integration").info("check2 condition: %s" % d_host.cn) return d_host.cn == "Testhost" base_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") with open(os.path.join(base_dir, "discovered_hosts", "mac00262df16a2c.json")) as f: mocked_foreman.register_conditional_response("http://localhost:8000/api/v2/discovered_hosts/mac00262df16a2c", "get", check, f.read()) with open(os.path.join(base_dir, "conditional", "Testhost.json")) as f: mocked_foreman.register_conditional_response("http://localhost:8000/api/v2/hosts/Testhost", "get", check2, f.read()) def activate(**kwargs): return True mocked_foreman.register_trigger("http://localhost:8000/api/v2/discovered_hosts/mac00262df16a2c", "put", activate, self.execute) with make_session() as session: assert session.query(ObjectInfoIndex.dn)\ .join(ObjectInfoIndex.properties)\ .filter(and_(KeyValueIndex.key == "cn", KeyValueIndex.value == "Testhost"))\ .count() == 0 d_host.cn = "Testhost" d_host.groupMembership = hostgroup.dn d_host.commit() logging.getLogger("test.foreman-integration").info("waiting for 2 seconds") await asyncio.sleep(2) logging.getLogger("test.foreman-integration").info("########### END: Add Host to group #############") # now move the host to the final destination d_host = ObjectProxy("cn=Testhost,ou=incoming,%s" % self._test_dn) assert d_host.status != "discovered" assert d_host.name == "Testhost" assert d_host.hostgroup_id == "4" assert d_host.is_extended_by("RegisteredDevice") is True assert len(d_host.userPassword[0]) > 0 assert d_host.deviceUUID is not None with make_session() as session: assert session.query(ObjectInfoIndex.dn) \ .join(ObjectInfoIndex.properties) \ .filter(and_(KeyValueIndex.key == "cn", KeyValueIndex.value == "Testhost")) \ .count() == 1 logging.getLogger("test.foreman-integration").info("########### START: moving host #############") d_host.move("%s" % self._test_dn) logging.getLogger("test.foreman-integration").info("########### END: moving host #############") # lets check if everything is fine in the database d_host = ObjectProxy("cn=Testhost,ou=devices,%s" % self._test_dn, read_only=True) assert d_host is not None assert d_host.status == "unknown" assert d_host.groupMembership == hostgroup.dn
def add_host(self, hostname, base=None): # create dn if base is None: index = PluginRegistry.getInstance("ObjectIndex") # get the IncomingDevice-Container res = index.search({"_type": "IncomingDeviceContainer", "_parent_dn": self.type_bases["ForemanHost"]}, {"dn": 1}) if len(res) > 0: base = res[0]["dn"] else: base = self.type_bases["ForemanHost"] ForemanBackend.modifier = "foreman" device = self.get_object("ForemanHost", hostname, create=False) if device is None: self.log.debug("Realm request: creating new host with hostname: %s" % hostname) device = ObjectProxy(base, "Device") device.extend("ForemanHost") device.cn = hostname # commit now to get a uuid device.commit() # re-open to get a clean object device = ObjectProxy(device.dn) else: self.log.debug("Realm request: use existing host with hostname: %s" % hostname) try: if not device.is_extended_by("ForemanHost"): device.extend("ForemanHost") # Generate random client key h, key, salt = generate_random_key() # While the client is going to be joined, generate a random uuid and an encoded join key if not device.is_extended_by("RegisteredDevice"): device.extend("RegisteredDevice") if not device.is_extended_by("simpleSecurityObject"): device.extend("simpleSecurityObject") if device.deviceUUID is None: cn = str(uuid.uuid4()) device.deviceUUID = cn else: cn = device.deviceUUID device.status = "pending" device.status_InstallationInProgress = True if device.userPassword is None: device.userPassword = [] elif device.otp is not None: device.userPassword.remove(device.otp) device.userPassword.append("{SSHA}" + encode(h.digest() + salt).decode()) # make sure the client has the access rights he needs client_service = PluginRegistry.getInstance("ClientService") client_service.applyClientRights(device.deviceUUID) device.commit() self.mark_for_parameter_setting(hostname, {"status": "added"}) return "%s|%s" % (key, cn) except Exception as e: # remove created device again because something went wrong # self.remove_type("ForemanHost", hostname) self.log.error(str(e)) raise e finally: ForemanBackend.modifier = None