def bind(self, consumer_id, repo_id, distributor_id, options): """ Request the agent to perform the specified bind. This method will be called after the server-side representation of the binding has been created. :param consumer_id: The consumer ID. :type consumer_id: str :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :param options: The options are handler specific. :type options: dict """ # agent request consumer_manager = managers.consumer_manager() binding_manager = managers.consumer_bind_manager() consumer = consumer_manager.get_consumer(consumer_id) binding = binding_manager.get_bind(consumer_id, repo_id, distributor_id) agent_bindings = self.__bindings([binding]) agent = PulpAgent(consumer) agent.consumer.bind(agent_bindings, options) # request tracking action_id = factory.context().call_request_id consumer_manager = managers.consumer_bind_manager() consumer_manager.action_pending( consumer_id, repo_id, distributor_id, Bind.Action.BIND, action_id)
def test_bind(self): # Setup self.populate() # Test options = {} itinerary = bind_itinerary( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG, options) call_reports = self.coordinator.execute_multiple_calls(itinerary) # Verify self.assertEqual(len(call_reports), 2) self.assertEqual(call_reports[0].call_request_tags, self.BIND_TAGS) self.assertEqual(call_reports[1].call_request_tags, self.AGENT_BIND_TAGS) for call in call_reports: self.assertNotEqual(call.state, dispatch_constants.CALL_REJECTED_RESPONSE) # run task #1 (actual bind) self.run_next() # verify bind created manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 1) bind = binds[0] self.assertEqual(bind['consumer_id'], self.CONSUMER_ID) self.assertEqual(bind['repo_id'], self.REPO_ID) self.assertEqual(bind['distributor_id'], self.DISTRIBUTOR_ID) self.assertEqual(bind['notify_agent'], self.NOTIFY_AGENT) self.assertEqual(bind['binding_config'], self.BINDING_CONFIG) # run task #2 (notify consumer) self.run_next() # verify pending consumer request (pending) request_id = call_reports[1].call_request_id bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind['consumer_actions'] self.assertEqual(len(actions), 1) self.assertEqual(actions[0]['id'], request_id) self.assertEqual(actions[0]['action'], Bind.Action.BIND) self.assertEqual(actions[0]['status'], Bind.Status.PENDING) self.assertTrue(isinstance(actions[0]['timestamp'], float)) # verify agent notified self.assertTrue(mock_agent.Consumer.bind.called) # simulated asynchronous task result report = DispatchReport() self.coordinator.complete_call_success(request_id, report.dict()) # verify pending consumer request (confirmed) manager = factory.consumer_bind_manager() bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) self.assertEqual(len(bind['consumer_actions']), 0)
def bind(consumer_id, repo_id, distributor_id, options): """ Request the agent to perform the specified bind. This method will be called after the server-side representation of the binding has been created. :param consumer_id: The consumer ID. :type consumer_id: str :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :param options: The options are handler specific. :type options: dict :return: The task created by the bind :rtype: dict """ # track agent operations using a pseudo task task_id = str(uuid4()) tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_AGENT_BIND) ] task = TaskStatusManager.create_task_status(task_id, 'agent', tags=tags) # agent request consumer_manager = managers.consumer_manager() binding_manager = managers.consumer_bind_manager() consumer = consumer_manager.get_consumer(consumer_id) binding = binding_manager.get_bind(consumer_id, repo_id, distributor_id) agent_bindings = AgentManager._bindings([binding]) context = Context( consumer, task_id=task_id, action='bind', consumer_id=consumer_id, repo_id=repo_id, distributor_id=distributor_id) agent = PulpAgent() agent.consumer.bind(context, agent_bindings, options) # bind action tracking consumer_manager = managers.consumer_bind_manager() consumer_manager.action_pending( consumer_id, repo_id, distributor_id, Bind.Action.BIND, task_id) return task
def test_consumer_unregister_cleanup(self): # Setup self.test_bind() manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 1) # Test manager = factory.consumer_manager() manager.unregister(self.CONSUMER_ID) # Verify manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 0)
def test_remove_repo_cleanup(self): # Setup self.test_bind() manager = factory.consumer_bind_manager() binds = manager.find_by_repo(self.REPO_ID) self.assertEquals(len(binds), 1) # Test manager = factory.repo_manager() manager.delete_repo(self.REPO_ID) # Verify manager = factory.consumer_bind_manager() binds = manager.find_by_repo(self.REPO_ID) self.assertEquals(len(binds), 0)
def test_remove_distributor_cleanup(self): # Setup self.test_bind() manager = factory.consumer_bind_manager() binds = manager.find_by_distributor(self.REPO_ID, self.DISTRIBUTOR_ID) self.assertEquals(len(binds), 1) # Test manager = factory.repo_distributor_manager() manager.remove_distributor(self.REPO_ID, self.DISTRIBUTOR_ID) # Verify manager = factory.consumer_bind_manager() binds = manager.find_by_distributor(self.REPO_ID, self.DISTRIBUTOR_ID) self.assertEquals(len(binds), 0)
def test_consumer_unregister_cleanup(self, *unused): self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEqual(len(binds), 1) # Test manager = factory.consumer_manager() manager.unregister(self.CONSUMER_ID) # Verify manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEqual(len(binds), 0)
def test_unbind(self, mock_repo_qs): # Setup self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) # Test manager = factory.consumer_bind_manager() manager.unbind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Verify collection = Bind.get_collection() bind_id = dict(consumer_id=self.CONSUMER_ID, repo_id=self.REPO_ID, distributor_id=self.DISTRIBUTOR_ID) bind = collection.find_one(bind_id) self.assertTrue(bind is not None) self.assertTrue(bind["deleted"])
def test_get_with_bindings(self): """ Test consumer with bindings. """ # Setup manager = factory.repo_manager() repo = manager.create_repo(self.REPO_ID) manager = factory.repo_distributor_manager() manager.add_distributor(self.REPO_ID, self.DISTRIBUTOR_TYPE_ID, {}, True, distributor_id=self.DISTRIBUTOR_ID) manager = factory.consumer_manager() manager.register(self.CONSUMER_ID) manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Test params = {"bindings": True} path = "/v2/consumers/%s/" % self.CONSUMER_ID status, body = self.get(path, params=params) # Verify self.assertEqual(200, status) self.assertEqual(self.CONSUMER_ID, body["id"]) self.assertTrue("_href" in body) self.assertTrue(body["_href"].endswith(path)) self.assertTrue("bindings" in body) bindings = body["bindings"] self.assertEquals(len(bindings), 1) self.assertEquals(bindings[0], self.REPO_ID)
def force_unbind(consumer_id, repo_id, distributor_id, options): """ Get the unbind itinerary. A forced unbind immediately deletes the binding instead of marking it deleted and going through that lifecycle. It is intended to be used to clean up orphaned bindings caused by failed/unconfirmed unbind actions on the consumer. The itinerary is: 1. Delete the binding on the server. 2. Request that the consumer (agent) perform the unbind. :param consumer_id: A consumer ID. :type consumer_id: str :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :param options: Unbind options passed to the agent handler. :type options: dict :returns TaskResult containing the result of the unbind & any spawned tasks or a dictionary of the unbind result if no tasks were spawned. :rtype: TaskResult """ bind_manager = managers.consumer_bind_manager() binding = bind_manager.get_bind(consumer_id, repo_id, distributor_id) bind_manager.delete(consumer_id, repo_id, distributor_id, True) response = None if binding['notify_agent']: agent_manager = managers.consumer_agent_manager() task = agent_manager.unbind(consumer_id, repo_id, distributor_id, options) response = TaskResult(spawned_tasks=[task]) return response
def test_delete(self): # Setup self.populate() manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Test manager.delete(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID)
def test_bind_action_failed(self, mock_repo_qs): self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) self.assertEqual(bind["consumer_actions"], []) for action_id in self.ACTION_IDS: manager.action_pending(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, Bind.Action.BIND, action_id) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind["consumer_actions"] for i in range(0, len(self.ACTION_IDS)): self.assertEqual(actions[i]["id"], self.ACTION_IDS[i]) self.assertEqual(actions[i]["action"], Bind.Action.BIND) self.assertEqual(actions[i]["status"], Bind.Status.PENDING) manager.action_failed(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.ACTION_IDS[5]) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind["consumer_actions"] self.assertEqual(len(actions), 9) for i in range(0, len(self.ACTION_IDS)): if i == 5: status = Bind.Status.FAILED else: status = Bind.Status.PENDING self.assertEqual(actions[i]["id"], self.ACTION_IDS[i]) self.assertEqual(actions[i]["action"], Bind.Action.BIND) self.assertEqual(actions[i]["status"], status) manager.action_succeeded(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.ACTION_IDS[6]) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind["consumer_actions"] self.assertEqual(len(actions), 2)
def test_get_bind_not_found(self, mock_repo_qs): # Setup self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) # Test self.assertRaises(MissingResource, manager.get_bind, "A", "B", "C")
def expand_consumers(options, consumers): """ Expand a list of users based on flags specified in the post body or query parameters. The _href is always added by the serialization function used. Supported options: details - synonym for: (bindings=True,) bindings - include bindings @param options: The (expanding) options. @type options: dict @param consumers: A list of consumers @type consumers: list @return: A list of expanded consumers. @rtype: list """ if options.get('details', False): options['bindings'] = True # add bindings if options.get('bindings', False): ids = [c['id'] for c in consumers] manager = managers.consumer_bind_manager() bindings = manager.find_by_consumer_list(ids) for consumer in consumers: id = consumer['id'] consumer['bindings'] = \ [b['repo_id'] for b in bindings.get(id, [])] return consumers
def test_forced_unbind(self, mock_unbind_itinerary): # Setup self.populate() manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Test path = '/v2/consumers/%s/bindings/%s/%s/' %\ (self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) body = {'force':True} status, body = self.delete(path, body) # Verify self.assertEquals(status, 202) self.assertEqual(len(body), 3) for call in body: self.assertNotEqual(call['state'], dispatch_constants.CALL_REJECTED_RESPONSE) # verify itinerary called mock_unbind_itinerary.assert_called_with( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, {}, True)
def populate(self, strategy=constants.DEFAULT_STRATEGY, ssl=False): PluginTestBase.populate(self) # register child manager = managers.consumer_manager() manager.register(self.PULP_ID, notes={constants.STRATEGY_NOTE_KEY: strategy}) manager = managers.repo_importer_manager() # add importer importer_conf = { constants.MANIFEST_URL_KEYWORD: 'http://redhat.com', constants.STRATEGY_KEYWORD: constants.DEFAULT_STRATEGY, constants.PROTOCOL_KEYWORD: 'file', } manager.set_importer(self.REPO_ID, constants.HTTP_IMPORTER, importer_conf) # add distributors if ssl: dist_conf = self.dist_conf_with_ssl() else: dist_conf = self.dist_conf() manager = managers.repo_distributor_manager() manager.add_distributor( self.REPO_ID, constants.HTTP_DISTRIBUTOR, dist_conf, False, constants.HTTP_DISTRIBUTOR) manager.add_distributor(self.REPO_ID, FAKE_DISTRIBUTOR, {}, False, FAKE_DISTRIBUTOR) # bind conf = {constants.STRATEGY_KEYWORD: strategy} manager = managers.consumer_bind_manager() manager.bind(self.PULP_ID, self.REPO_ID, constants.HTTP_DISTRIBUTOR, False, conf)
def expand_consumers(options, consumers): """ Expand a list of users based on flags specified in the post body or query parameters. The _href is always added by the serialization function used. Supported options: details - synonym for: (bindings=True,) bindings - include bindings @param options: The (expanding) options. @type options: dict @param consumers: A list of consumers @type consumers: list @return: A list of expanded consumers. @rtype: list """ if options.get('details', False): options['bindings'] = True # add bindings if options.get('bindings', False): ids = [c['id'] for c in consumers] manager = managers.consumer_bind_manager() criteria = Criteria({'consumer_id': {'$in': ids}}) bindings = manager.find_by_criteria(criteria) collated = {} for b in bindings: lst = collated.setdefault(b['consumer_id'], []) lst.append(b) for _consumer in consumers: _consumer['bindings'] = \ [serialization.binding.serialize(b, False) for b in collated.get(_consumer['id'], [])] return consumers
def GET(self, consumer_id, repo_id=None): """ Fetch all bind objects referencing the specified consumer_id. Optionally, specify a repo_id to fetch all bind objects for the consumer_id to the repo_id :param consumer_id: The specified consumer. :type consumer_id: str :param repo_id: The repository to retrieve bindings for (optional) :type repo_id: str :return: A list of dictionaries that represent pulp.server.db.model.consumer.Bind objects :rtype: list """ # Check to make sure the resources exist missing_resources = {} if repo_id is not None: repo = managers.repo_query_manager().find_by_id(repo_id) if repo is None: missing_resources['repo_id'] = repo_id # If get_consumer raises MissingResource we might miss reporting a bad repo_id try: managers.consumer_manager().get_consumer(consumer_id) except MissingResource: missing_resources['consumer_id'] = consumer_id if len(missing_resources) > 0: raise MissingResource(**missing_resources) manager = managers.consumer_bind_manager() bindings = manager.find_by_consumer(consumer_id, repo_id) bindings = [serialization.binding.serialize(b) for b in bindings] return self.ok(bindings)
def delete(repo_id): """ Get the itinerary for deleting a repository. 1. Delete the repository on the sever. 2. Unbind any bound consumers. :param repo_id: A repository ID. :type repo_id: str :return: A TaskRequest object with the details of any errors or spawned tasks :rtype TaskRequest """ # delete repository manager = managers.repo_manager() manager.delete_repo(repo_id) # append unbind itineraries foreach bound consumer options = {} manager = managers.consumer_bind_manager() additional_tasks = [] errors = [] for bind in manager.find_by_repo(repo_id): try: report = consumer.unbind(bind["consumer_id"], bind["repo_id"], bind["distributor_id"], options) if report: additional_tasks.extend(report.spawned_tasks) except Exception, e: errors.append(e)
def test_bind_request_succeeded(self): # Setup self.populate() # Test manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) self.assertEqual(bind['consumer_actions'], []) for action_id in self.ACTION_IDS: manager.action_pending( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, Bind.Action.BIND, action_id) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind['consumer_actions'] for i in range(0, len(self.ACTION_IDS)): self.assertEqual(actions[i]['id'], self.ACTION_IDS[i]) self.assertEqual(actions[i]['action'], Bind.Action.BIND) self.assertEqual(actions[i]['status'], Bind.Status.PENDING) manager.action_succeeded( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.ACTION_IDS[4]) bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind['consumer_actions'] self.assertEqual(len(actions), 4)
def distributor_delete(repo_id, distributor_id): """ Get the itinerary for deleting a repository distributor. 1. Delete the distributor on the sever. 2. Unbind any bound consumers. :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor id :type distributor_id: str :return: Any errors that may have occurred and the list of tasks spawned for each consumer :rtype TaskResult """ # delete distributor manager = managers.repo_distributor_manager() manager.remove_distributor(repo_id, distributor_id) # append unbind itineraries foreach bound consumer unbind_errors = [] additional_tasks = [] options = {} manager = managers.consumer_bind_manager() for bind in manager.find_by_distributor(repo_id, distributor_id): try: report = consumer.unbind(bind["consumer_id"], bind["repo_id"], bind["distributor_id"], options) if report: additional_tasks.extend(report.spawned_tasks) except Exception, e: unbind_errors.append(e)
def repo_delete_itinerary(repo_id): """ Get the itinerary for deleting a repository. 1. Delete the repository on the sever. 2. Unbind any bound consumers. @param repo_id: A repository ID. @type repo_id: str @return: A list of call_requests known as an itinerary. @rtype list """ call_requests = [] # delete repository manager = managers.repo_manager() resources = {dispatch_constants.RESOURCE_REPOSITORY_TYPE: {repo_id: dispatch_constants.RESOURCE_DELETE_OPERATION}} tags = [resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), action_tag("delete")] delete_request = CallRequest(manager.delete_repo, [repo_id], resources=resources, tags=tags, archive=True) call_requests.append(delete_request) # append unbind itineraries foreach bound consumer options = {} manager = managers.consumer_bind_manager() for bind in manager.find_by_repo(repo_id): unbind_requests = unbind_itinerary(bind["consumer_id"], bind["repo_id"], bind["distributor_id"], options) if unbind_requests: unbind_requests[0].depends_on(delete_request.id) call_requests.extend(unbind_requests) return call_requests
def test_get_bind_not_found(self): # Setup self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Test self.assertRaises(MissingResource, manager.get_bind, 'A', 'B', 'C')
def test_consumer_unregister_cleanup(self): # Setup self.populate() # Test manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 1) # Test manager = factory.consumer_manager() manager.unregister(self.CONSUMER_ID) # Verify manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 0)
def delete(repo_id, dist_id): """ Removes a distributor from a repository and unbinds any bound consumers. :param distributor: distributor to be deleted :type distributor: pulp.server.db.model.Distributor :return: result containing any errors and tasks spawned :rtype pulp.server.async.tasks.TaskResult """ distributor = model.Distributor.objects.get_or_404(repo_id=repo_id, distributor_id=dist_id) managers.repo_publish_schedule_manager().delete_by_distributor_id(repo_id, dist_id) # Call the distributor's cleanup method dist_instance, plugin_config = plugin_api.get_distributor_by_id(distributor.distributor_type_id) call_config = PluginCallConfiguration(plugin_config, distributor.config) repo = model.Repository.objects.get_repo_or_missing_resource(repo_id) dist_instance.distributor_removed(repo.to_transfer_repo(), call_config) distributor.delete() unbind_errors = [] additional_tasks = [] options = {} bind_manager = managers.consumer_bind_manager() for bind in bind_manager.find_by_distributor(repo_id, dist_id): try: report = bind_manager.unbind(bind['consumer_id'], bind['repo_id'], bind['distributor_id'], options) if report: additional_tasks.extend(report.spawned_tasks) except Exception, e: unbind_errors.append(e)
def unbind(self, consumer_id, repo_id, distributor_id, options): """ Request the agent to perform the specified unbind. @param consumer_id: The consumer ID. @type consumer_id: str @param repo_id: A repository ID. @type repo_id: str @param distributor_id: A distributor ID. @type distributor_id: str @param options: The options are handler specific. @type options: dict """ # agent request manager = managers.consumer_manager() consumer = manager.get_consumer(consumer_id) binding = dict(repo_id=repo_id, distributor_id=distributor_id) bindings = self.__unbindings([binding]) agent = PulpAgent(consumer) agent.consumer.unbind(bindings, options) # request tracking action_id = factory.context().call_request_id manager = managers.consumer_bind_manager() manager.action_pending( consumer_id, repo_id, distributor_id, Bind.Action.UNBIND, action_id)
def test_forced_unbind_agent_not_notified(self): # Setup self.populate() manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, False, self.BINDING_CONFIG) # Test options = {} itinerary = forced_unbind_itinerary( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, options) call_reports = self.coordinator.execute_multiple_calls(itinerary) # Verify self.assertEqual(len(call_reports), 1) self.assertEqual(call_reports[0].call_request_tags, self.UNBIND_TAGS) for call in call_reports: self.assertNotEqual(call.state, dispatch_constants.CALL_REJECTED_RESPONSE) # run task #1 (actual delete) self.run_next() # verify bind deleted collection = Bind.get_collection() bind = collection.find_one(self.QUERY) self.assertTrue(bind is None) # verify agent notified self.assertFalse(mock_agent.Consumer.unbind.called)
def DELETE(self, consumer_id, repo_id, distributor_id): """ Delete a bind association between the specified consumer and repo-distributor. Designed to be idempotent. @param consumer_id: A consumer ID. @type consumer_id: str @param repo_id: A repo ID. @type repo_id: str @param distributor_id: A distributor ID. @type distributor_id: str @return: The list of call_reports @rtype: list """ body = self.params() # validate resources manager = managers.consumer_bind_manager() binding = manager.get_bind(consumer_id, repo_id, distributor_id) notify_agent = binding['notify_agent'] # delete (unbind) forced = body.get('force', False) options = body.get('options', {}) if forced or not notify_agent: call_requests = forced_unbind_itinerary( consumer_id, repo_id, distributor_id, options) else: call_requests = unbind_itinerary( consumer_id, repo_id, distributor_id, options) execution.execute_multiple(call_requests)
def test_find_by_criteria(self, mock_repo_qs): self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) # Test criteria = Criteria({'consumer_id': self.CONSUMER_ID}) bindings = manager.find_by_criteria(criteria) bind = bindings[0] self.assertEqual(len(bindings), 1) self.assertEqual(bind['consumer_id'], self.CONSUMER_ID) self.assertEqual(bind['repo_id'], self.REPO_ID) self.assertEqual(bind['distributor_id'], self.DISTRIBUTOR_ID) self.assertEqual(bind['notify_agent'], self.NOTIFY_AGENT) self.assertEqual(bind['binding_config'], self.BINDING_CONFIG) # Test ($in) criteria = Criteria({'consumer_id': {'$in': [self.CONSUMER_ID]}}) bindings = manager.find_by_criteria(criteria) bind = bindings[0] self.assertEqual(len(bindings), 1) self.assertEqual(bind['consumer_id'], self.CONSUMER_ID) self.assertEqual(bind['repo_id'], self.REPO_ID) self.assertEqual(bind['distributor_id'], self.DISTRIBUTOR_ID) self.assertEqual(bind['notify_agent'], self.NOTIFY_AGENT) self.assertEqual(bind['binding_config'], self.BINDING_CONFIG)
def test_get_with_bindings(self): """ Test consumer with bindings. """ # Setup manager = factory.repo_manager() repo = manager.create_repo(self.REPO_ID) manager = factory.repo_distributor_manager() manager.add_distributor( self.REPO_ID, self.DISTRIBUTOR_TYPE_ID, {}, True, distributor_id=self.DISTRIBUTOR_ID) manager = factory.consumer_manager() manager.register(self.CONSUMER_ID) manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Test params = {'bindings':True} path = '/v2/consumers/%s/' % self.CONSUMER_ID status, body = self.get(path, params=params) # Verify self.assertEqual(200, status) self.assertEqual(self.CONSUMER_ID, body['id']) self.assertTrue('_href' in body) self.assertTrue(body['_href'].endswith(path)) self.assertTrue('bindings' in body) bindings = body['bindings'] self.assertEquals(len(bindings), 1) self.assertEquals(bindings[0]['repo_id'], self.REPO_ID) self.assertEquals(bindings[0]['distributor_id'], self.DISTRIBUTOR_ID) self.assertEquals(bindings[0]['consumer_actions'], [])
def test_delete(self): # Setup self.populate() manager = factory.consumer_bind_manager() for consumer_id in self.ALL_CONSUMERS: manager.bind(consumer_id, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) manager.action_pending(self.EXTRA_CONSUMER_1, self.REPO_ID, self.DISTRIBUTOR_ID, Bind.Action.BIND, '1') manager.action_pending(self.EXTRA_CONSUMER_2, self.REPO_ID, self.DISTRIBUTOR_ID, Bind.Action.BIND, '2') # Test manager.mark_deleted(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) manager.delete(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) # Verify self.assertRaises(MissingResource, manager.get_bind, self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) manager.get_bind(self.EXTRA_CONSUMER_1, self.REPO_ID, self.DISTRIBUTOR_ID) manager.get_bind(self.EXTRA_CONSUMER_2, self.REPO_ID, self.DISTRIBUTOR_ID)
def get(self, request, consumer_id, repo_id, distributor_id): """ Fetch a specific bind object which represents a specific association between a consumer and repo-distributor. :param request: WSGI request object :type request: django.core.handlers.wsgi.WSGIRequest :param consumer_id: A consumer ID. :type consumer_id: str :param repo_id: A repo ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :return: Response representing the binding :rtype: django.http.HttpResponse """ manager = factory.consumer_bind_manager() bind = manager.get_bind(consumer_id, repo_id, distributor_id) serialized_bind = serialization.binding.serialize(bind) return generate_json_response_with_pulp_encoder(serialized_bind)
def bind(consumer_id, repo_id, distributor_id, notify_agent, binding_config, agent_options): """ Bind a repo to a consumer: 1. Create the binding on the server. 2. Request that the consumer (agent) perform the bind. :param consumer_id: A consumer ID. :type consumer_id: str :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :param agent_options: Bind options passed to the agent handler. :type agent_options: dict :param notify_agent: indicates if the agent should be sent a message about the new binding :type notify_agent: bool :param binding_config: configuration options to use when generating the payload for this binding :returns TaskResult containing the result of the bind & any spawned tasks or a dictionary of the bind result if no tasks were spawned. :rtype: TaskResult :raises pulp.server.exceptions.MissingResource: when given consumer does not exist """ # Create the binding on the server bind_manager = managers.consumer_bind_manager() binding = bind_manager.bind(consumer_id, repo_id, distributor_id, notify_agent, binding_config) response = TaskResult(result=binding) # Notify the agents of the binding if notify_agent: agent_manager = managers.consumer_agent_manager() task = agent_manager.bind(consumer_id, repo_id, distributor_id, agent_options) response.spawned_tasks.append(task) return response
def delete(repo_id, dist_id): """ Removes a distributor from a repository and unbinds any bound consumers. :param distributor: distributor to be deleted :type distributor: pulp.server.db.model.Distributor :return: result containing any errors and tasks spawned :rtype pulp.server.async.tasks.TaskResult """ distributor = model.Distributor.objects.get_or_404(repo_id=repo_id, distributor_id=dist_id) managers.repo_publish_schedule_manager().delete_by_distributor_id( repo_id, dist_id) # Call the distributor's cleanup method dist_instance, plugin_config = plugin_api.get_distributor_by_id( distributor.distributor_type_id) call_config = PluginCallConfiguration(plugin_config, distributor.config) repo = model.Repository.objects.get_repo_or_missing_resource(repo_id) dist_instance.distributor_removed(repo.to_transfer_repo(), call_config) distributor.delete() unbind_errors = [] additional_tasks = [] options = {} bind_manager = managers.consumer_bind_manager() for bind in bind_manager.find_by_distributor(repo_id, dist_id): try: report = bind_manager.unbind(bind['consumer_id'], bind['repo_id'], bind['distributor_id'], options) if report: additional_tasks.extend(report.spawned_tasks) except Exception, e: unbind_errors.append(e)
def get(self, request, consumer_id, repo_id=None): """ Fetch all bind objects referencing the specified consumer_id. Optionally, specify a repo_id to fetch all bind objects for the consumer_id to the repo_id. :param request: WSGI request object :type request: django.core.handlers.wsgi.WSGIRequest :param consumer_id: The specified consumer. :type consumer_id: str :param repo_id: The repository to retrieve bindings for (optional) :type repo_id: str :raises MissingResource: if some resource is missing :return: Response representing the bindings :rtype: django.http.HttpResponse """ # Check to make sure the resources exist missing_resources = {} if repo_id is not None: repo = model.Repository.objects(repo_id).first() if repo is None: missing_resources['repo_id'] = repo_id # If get_consumer raises MissingResource we might miss reporting a bad repo_id try: factory.consumer_manager().get_consumer(consumer_id) except MissingResource: missing_resources['consumer_id'] = consumer_id if missing_resources: raise MissingResource(**missing_resources) manager = factory.consumer_bind_manager() bindings = manager.find_by_consumer(consumer_id, repo_id) bindings = [serial_binding.serialize(b) for b in bindings] return generate_json_response_with_pulp_encoder(bindings)
def GET(self, consumer_id, repo_id, distributor_id): """ Fetch a specific bind object which represents a specific association between a consumer and repo-distributor. @param consumer_id: A consumer ID. @type consumer_id: str @param repo_id: A repo ID. @type repo_id: str @param distributor_id: A distributor ID. @type distributor_id: str @return: A specific bind object: {consumer_id:<str>, repo_id:<str>, distributor_id:<str>, href:<str>, type_id:<str>, details:<dict>} @rtype: dict """ manager = managers.consumer_bind_manager() bind = manager.get_bind(consumer_id, repo_id, distributor_id) serialized_bind = serialization.binding.serialize(bind) return self.ok(serialized_bind)
def force_unbind(consumer_id, repo_id, distributor_id, options): """ Get the unbind itinerary. A forced unbind immediately deletes the binding instead of marking it deleted and going through that lifecycle. It is intended to be used to clean up orphaned bindings caused by failed/unconfirmed unbind actions on the consumer. The itinerary is: 1. Delete the binding on the server. 2. Request that the consumer (agent) perform the unbind. :param consumer_id: A consumer ID. :type consumer_id: str :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :param options: Unbind options passed to the agent handler. :type options: dict :returns TaskResult containing the result of the unbind & any spawned tasks or a dictionary of the unbind result if no tasks were spawned. :rtype: TaskResult """ bind_manager = managers.consumer_bind_manager() binding = bind_manager.get_bind(consumer_id, repo_id, distributor_id) bind_manager.delete(consumer_id, repo_id, distributor_id, True) response = TaskResult() if binding['notify_agent']: agent_manager = managers.consumer_agent_manager() task = agent_manager.unbind(consumer_id, repo_id, distributor_id, options) # we only want the task's ID, not the full task response.spawned_tasks.append({'task_id': task['task_id']}) return response
def test_forced_unbind(self): # Setup self.populate() manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) # Test options = {} itinerary = forced_unbind_itinerary( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, options) call_reports = self.coordinator.execute_multiple_calls(itinerary) # Verify self.assertEqual(len(call_reports), 2) self.assertEqual(call_reports[0].call_request_tags, self.UNBIND_TAGS) self.assertEqual(call_reports[1].call_request_tags, self.AGENT_UNBIND_TAGS) for call in call_reports: self.assertNotEqual(call.state, dispatch_constants.CALL_REJECTED_RESPONSE) # run task #1 (actual delete) self.run_next() # verify bind marked deleted collection = Bind.get_collection() bind = collection.find_one(self.QUERY) self.assertTrue(bind is None) # run task #2 (notify consumer) self.run_next() # verify agent notified self.assertTrue(mock_agent.Consumer.unbind.called)
def expand_consumers(details, bindings, consumers): """ Expand a list of users based on the flag specified in the query parameters. The _href is always added by the serialization function used. Supported options: details - include details bindings - include bindings :param details: if True, details will be included in the response :type details: bool :param bindings: if True, bindings will be included with each returned consumer :type bindings: bool :param consumers: A list of consumers :type consumers: list :return: A list of expanded consumers. :rtype: list """ if details: bindings = True # add bindings if bindings: ids = [c['id'] for c in consumers] manager = factory.consumer_bind_manager() criteria = Criteria({'consumer_id': {'$in': ids}}) bindings = manager.find_by_criteria(criteria) collated = {} for b in bindings: lst = collated.setdefault(b['consumer_id'], []) lst.append(b) for c in consumers: c['bindings'] = [ serial_binding.serialize(b, False) for b in collated.get(c['id'], []) ] return consumers
def unbind(self, consumer_id, repo_id, distributor_id, options): """ Request the agent to perform the specified unbind. :param consumer_id: The consumer ID. :type consumer_id: str :param repo_id: A repository ID. :type repo_id: str :param distributor_id: A distributor ID. :type distributor_id: str :param options: The options are handler specific. :type options: dict """ # agent request manager = managers.consumer_manager() consumer = manager.get_consumer(consumer_id) binding = dict(repo_id=repo_id, distributor_id=distributor_id) bindings = self.__unbindings([binding]) agent = PulpAgent(consumer) agent.consumer.unbind(bindings, options) # request tracking action_id = factory.context().call_request_id manager = managers.consumer_bind_manager() manager.action_pending(consumer_id, repo_id, distributor_id, Bind.Action.UNBIND, action_id)
def expand_consumers(options, consumers): """ Expand a list of users based on the flag specified in the query parameters. The _href is always added by the serialization function used. Supported options: details - include details bindings - include bindings :param options: The (expanding) options. :type options: dict :param consumers: A list of consumers :type consumers: list :return: A list of expanded consumers. :rtype: list """ details = options.get('details', 'false').lower() == 'true' bindings = options.get('bindings', 'false').lower() == 'true' if details: bindings = True # add bindings if bindings: ids = [c['id'] for c in consumers] manager = factory.consumer_bind_manager() criteria = Criteria({'consumer_id': {'$in': ids}}) bindings = manager.find_by_criteria(criteria) collated = {} for b in bindings: lst = collated.setdefault(b['consumer_id'], []) lst.append(b) for c in consumers: c['bindings'] = [ serialization.binding.serialize(b, False) for b in collated.get(c['id'], []) ] return consumers
def test_search(self): # Setup self.populate() manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) manager.action_pending(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, Bind.Action.BIND, '0') # Test criteria = { 'filters': { 'consumer_actions.status': { '$in': ['pending', 'failed'] } } } path = '/v2/consumers/binding/search/' body = dict(criteria=criteria) status, body = self.post(path, body) # Verify self.assertEqual(status, 200) self.assertEqual(len(body), 1)
def update(repo_id, dist_id, config=None, delta=None): """ Update the distributor and (re)bind any bound consumers. :param distributor: distributor to be updated :type distributor: pulp.server.db.model.Distributor :param config: A configuration dictionary for a distributor instance. The contents of this dict depends on the type of distributor. Values of None will remove they key from the config. Keys ommited from this dictionary will remain unchanged. :type config: dict :param delta: A dictionary used to change conf values for a distributor instance. This currently only supports the 'auto_publish' keyword, which should have a value of type bool :type delta: dict or None :return: result containing any errors and tasks spawned :rtype pulp.server.async.tasks.TaskResult """ repo = model.Repository.objects.get_repo_or_missing_resource(repo_id) distributor = model.Distributor.objects.get_or_404(repo_id=repo_id, distributor_id=dist_id) for k, v in config.iteritems(): if v is None: distributor.config.pop(k) else: distributor.config[k] = v auto_publish = delta.get('auto_publish') if delta else None if isinstance(auto_publish, bool): distributor.auto_publish = auto_publish elif not isinstance(auto_publish, type(None)): raise exceptions.InvalidValue(['auto_publish']) # Let the distributor plugin verify the configuration distributor_instance, plugin_config = plugin_api.get_distributor_by_id( distributor.distributor_type_id) call_config = PluginCallConfiguration(plugin_config, distributor.config) transfer_repo = repo.to_transfer_repo() config_conduit = RepoConfigConduit(distributor.distributor_type_id) result = distributor_instance.validate_config(transfer_repo, call_config, config_conduit) # For backward compatibility with plugins that don't yet return the tuple if isinstance(result, bool): valid_config = result message = None else: valid_config, message = result if not valid_config: raise exceptions.PulpDataException(message) distributor.save() unbind_errors = [] additional_tasks = [] options = {} bind_manager = managers.consumer_bind_manager() for bind in bind_manager.find_by_distributor(distributor.repo_id, distributor.distributor_id): try: report = bind_manager.bind(bind['consumer_id'], bind['repo_id'], bind['distributor_id'], bind['notify_agent'], bind['binding_config'], options) if report: additional_tasks.extend(report.spawned_tasks) except Exception, e: unbind_errors.append(e)
def regenerate_applicability_for_consumers(consumer_criteria): """ Regenerate and save applicability data for given updated consumers. :param consumer_criteria: The consumer selection criteria :type consumer_criteria: dict """ consumer_criteria = Criteria.from_dict(consumer_criteria) consumer_query_manager = managers.consumer_query_manager() bind_manager = managers.consumer_bind_manager() consumer_profile_manager = managers.consumer_profile_manager() # Process consumer_criteria and get all the consumer ids satisfied by the criteria consumer_criteria.fields = ['id'] consumer_ids = [ c['id'] for c in consumer_query_manager.find_by_criteria(consumer_criteria) ] # Following logic of checking existing applicability and getting required data # to generate applicability is a bit more complicated than what it could be 'by design'. # It is to optimize the number of db queries and improving applicability generation # performance. Please consider the implications for applicability generation time # when making any modifications to this code. # Get all unit profiles associated with given consumers unit_profile_criteria = Criteria( filters={'consumer_id': { '$in': consumer_ids }}, fields=['consumer_id', 'profile_hash', 'content_type', 'id']) all_unit_profiles = consumer_profile_manager.find_by_criteria( unit_profile_criteria) # Create a consumer-profile map with consumer id as the key and list of tuples # with profile details as the value consumer_unit_profiles_map = {} # Also create a map of profile_id keyed by profile_hash for profile lookup. profile_hash_profile_id_map = {} for unit_profile in all_unit_profiles: profile_hash = unit_profile['profile_hash'] content_type = unit_profile['content_type'] consumer_id = unit_profile['consumer_id'] profile_id = unit_profile['id'] profile_tuple = (profile_hash, content_type) # Add this tuple to the list of profile tuples for a consumer consumer_unit_profiles_map.setdefault(consumer_id, []).append(profile_tuple) # We need just one profile_id per profile_hash to be used in regenerate_applicability # method to get the actual profile corresponding to given profile_hash. if profile_hash not in profile_hash_profile_id_map: profile_hash_profile_id_map[profile_hash] = profile_id # Get all repos bound to given consumers bind_criteria = Criteria( filters={'consumer_id': { '$in': consumer_ids }}, fields=['repo_id', 'consumer_id']) all_repo_bindings = bind_manager.find_by_criteria(bind_criteria) # Create a repo-consumer map with repo_id as the key and consumer_id list as the value repo_consumers_map = {} for binding in all_repo_bindings: repo_consumers_map.setdefault(binding['repo_id'], []).append(binding['consumer_id']) # Create a set of (repo_id, (profile_hash, content_type)) repo_profile_hashes = set() for repo_id, consumer_id_list in repo_consumers_map.items(): for consumer_id in consumer_id_list: if consumer_id in consumer_unit_profiles_map: for unit_profile_tuple in consumer_unit_profiles_map[ consumer_id]: repo_profile_hashes.add((repo_id, unit_profile_tuple)) # Iterate through each tuple in repo_profile_hashes set and regenerate applicability, # if it doesn't exist. These are all guaranteed to be unique tuples because of the logic # used to create maps and sets above, eliminating multiple unnecessary queries # to check for existing applicability for same profiles. manager = managers.applicability_regeneration_manager() for repo_id, (profile_hash, content_type) in repo_profile_hashes: # Check if applicability for given profile_hash and repo_id already exists if ApplicabilityRegenerationManager._is_existing_applicability( repo_id, profile_hash): continue # If applicability does not exist, generate applicability data for given profile # and repo id. profile_id = profile_hash_profile_id_map[profile_hash] manager.regenerate_applicability(profile_hash, content_type, profile_id, repo_id)
def bind_itinerary(consumer_id, repo_id, distributor_id, notify_agent, binding_config, agent_options): """ Get the bind itinerary: 1. Create the binding on the server. 2. Request that the consumer (agent) perform the bind. @param consumer_id: A consumer ID. @type consumer_id: str @param repo_id: A repository ID. @type repo_id: str @param distributor_id: A distributor ID. @type distributor_id: str @param agent_options: Bind options passed to the agent handler. @type agent_options: dict @param notify_agent: indicates if the agent should be sent a message about the new binding @type notify_agent: bool @param binding_config: configuration options to use when generating the payload for this binding @return: A list of call_requests. @rtype list """ call_requests = [] bind_manager = managers.consumer_bind_manager() agent_manager = managers.consumer_agent_manager() # bind tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_BIND) ] args = [ consumer_id, repo_id, distributor_id, notify_agent, binding_config, ] bind_request = CallRequest(bind_manager.bind, args, weight=0, tags=tags) bind_request.reads_resource(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id) bind_request.reads_resource(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id) bind_request.reads_resource( dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id) call_requests.append(bind_request) # notify agent if notify_agent: tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag( dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_AGENT_BIND) ] args = [consumer_id, repo_id, distributor_id, agent_options] agent_request = CallRequest(agent_manager.bind, args, weight=0, asynchronous=True, archive=True, tags=tags) agent_request.add_life_cycle_callback( dispatch_constants.CALL_SUCCESS_LIFE_CYCLE_CALLBACK, bind_succeeded) agent_request.add_life_cycle_callback( dispatch_constants.CALL_FAILURE_LIFE_CYCLE_CALLBACK, bind_failed) call_requests.append(agent_request) agent_request.depends_on(bind_request.id) return call_requests
msg = msg % {'r': repo_id} _logger.exception(msg) error_tuples.append(e) # remove the repo from any groups it was a member of group_manager = manager_factory.repo_group_manager() group_manager.remove_repo_from_groups(repo_id) if len(error_tuples) > 0: pe = pulp_exceptions.PulpExecutionException() pe.child_exceptions = error_tuples raise pe # append unbind itineraries foreach bound consumer options = {} consumer_bind_manager = manager_factory.consumer_bind_manager() additional_tasks = [] errors = [] for bind in consumer_bind_manager.find_by_repo(repo_id): try: report = consumer_controller.unbind(bind['consumer_id'], bind['repo_id'], bind['distributor_id'], options) if report: additional_tasks.extend(report.spawned_tasks) except Exception, e: errors.append(e) error = None
def populate_bindings(self): manager = factory.consumer_bind_manager() manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG)
def test_unbind_failed_on_consumer(self): # Setup self.populate() manager = factory.consumer_bind_manager() bind = manager.bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG) # Test options = {} itinerary = unbind_itinerary( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, options) call_reports = self.coordinator.execute_multiple_calls(itinerary) # Verify self.assertEqual(len(call_reports), 3) for call in call_reports: self.assertNotEqual(call.state, dispatch_constants.CALL_REJECTED_RESPONSE) # run task #1 (actual unbind) self.run_next() # verify bind marked deleted collection = Bind.get_collection() bind = collection.find_one(self.QUERY) self.assertTrue(bind['deleted']) # run task #2 (notify consumer) self.run_next() # verify agent notified self.assertTrue(mock_agent.Consumer.unbind.called) # verify consumer request (pending) request_id = call_reports[1].call_request_id collection = Bind.get_collection() bind = collection.find_one(self.QUERY) self.assertTrue(bind is not None) actions = bind['consumer_actions'] self.assertEqual(len(actions), 1) self.assertEqual(actions[0]['id'], request_id) self.assertEqual(actions[0]['action'], Bind.Action.UNBIND) self.assertEqual(actions[0]['status'], Bind.Status.PENDING) self.assertTrue(isinstance(actions[0]['timestamp'], float)) # simulated asynchronous task result report = DispatchReport() report.succeeded = False self.coordinator.complete_call_success(request_id, report.dict()) # verify not found (marked deleted) binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 0) # run task #3 (bind actually deleted) self.run_next() # verify bind not deleted collection = Bind.get_collection() bind = collection.find_one(self.QUERY) self.assertTrue(bind is not None) actions = bind['consumer_actions'] self.assertEqual(len(actions), 1) self.assertEqual(actions[0]['id'], request_id) self.assertEqual(actions[0]['action'], Bind.Action.UNBIND) self.assertEqual(actions[0]['status'], Bind.Status.FAILED) self.assertTrue(isinstance(actions[0]['timestamp'], float))
def forced_unbind_itinerary(consumer_id, repo_id, distributor_id, options): """ Get the unbind itinerary. A forced unbind immediately deletes the binding instead of marking it deleted and going through that lifecycle. It is intended to be used to clean up orphaned bindings caused by failed/unconfirmed unbind actions on the consumer. The itinerary is: 1. Delete the binding on the server. 2. Request that the consumer (agent) perform the unbind. @param consumer_id: A consumer ID. @type consumer_id: str @param repo_id: A repository ID. @type repo_id: str @param distributor_id: A distributor ID. @type distributor_id: str @param options: Unbind options passed to the agent handler. @type options: dict @return: A list of call_requests @rtype list """ call_requests = [] bind_manager = managers.consumer_bind_manager() agent_manager = managers.consumer_agent_manager() # unbind tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_UNBIND) ] args = [ consumer_id, repo_id, distributor_id, True, ] delete_request = CallRequest(bind_manager.delete, args=args, tags=tags) delete_request.reads_resource(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id) call_requests.append(delete_request) # notify agent tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_AGENT_UNBIND) ] args = [ consumer_id, repo_id, distributor_id, options, ] agent_request = CallRequest(agent_manager.unbind, args, weight=0, asynchronous=True, archive=True, tags=tags) call_requests.append(agent_request) agent_request.depends_on(delete_request.id) return call_requests
def __init__(self): SearchController.__init__(self, managers.consumer_bind_manager().find_by_criteria)
def unbind_itinerary(consumer_id, repo_id, distributor_id, options): """ Get the unbind itinerary. The tasks in the itinerary are as follows: 1. Mark the binding as (deleted) on the server. 2. Request that the consumer (agent) perform the unbind. 3. Delete the binding on the server. @param consumer_id: A consumer ID. @type consumer_id: str @param repo_id: A repository ID. @type repo_id: str @param distributor_id: A distributor ID. @type distributor_id: str @param options: Unbind options passed to the agent handler. @type options: dict @return: A list of call_requests. @rtype list """ call_requests = [] bind_manager = managers.consumer_bind_manager() agent_manager = managers.consumer_agent_manager() # unbind tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_UNBIND) ] args = [ consumer_id, repo_id, distributor_id, ] unbind_request = CallRequest(bind_manager.unbind, args=args, tags=tags) unbind_request.reads_resource(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id) call_requests.append(unbind_request) # notify agent tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_AGENT_UNBIND) ] args = [ consumer_id, repo_id, distributor_id, options, ] agent_request = CallRequest(agent_manager.unbind, args, weight=0, asynchronous=True, archive=True, tags=tags) agent_request.add_life_cycle_callback( dispatch_constants.CALL_SUCCESS_LIFE_CYCLE_CALLBACK, unbind_succeeded) agent_request.add_life_cycle_callback( dispatch_constants.CALL_FAILURE_LIFE_CYCLE_CALLBACK, unbind_failed) call_requests.append(agent_request) agent_request.depends_on(unbind_request.id) # delete the binding tags = [ resource_tag(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_TYPE, repo_id), resource_tag(dispatch_constants.RESOURCE_REPOSITORY_DISTRIBUTOR_TYPE, distributor_id), action_tag(ACTION_DELETE_BINDING) ] args = [consumer_id, repo_id, distributor_id] delete_request = CallRequest(bind_manager.delete, args=args, tags=tags) unbind_request.reads_resource(dispatch_constants.RESOURCE_CONSUMER_TYPE, consumer_id) call_requests.append(delete_request) delete_request.depends_on(agent_request.id) return call_requests
def find_applicable_units(self, consumer_criteria=None, repo_criteria=None, unit_criteria=None, override_config=None): """ Determine and report which of the content units specified by the unit_criteria are applicable to consumers specified by the consumer_criteria with repos specified by repo_criteria. If consumer_criteria is None, all consumers registered to the Pulp server are checked for applicability. If repo_criteria is None, all repos bound to the consumer are taken into consideration. If unit_criteria contains an empty list for a specific type, all units with specific type in the repos bound to the consumer are taken into consideration. :param consumer_criteria: The consumer selection criteria. :type consumer_criteria: dict :param repo_criteria: The repo selection criteria. :type repo_criteria: dict :param unit_criteria: A dictionary of type_id : unit selection criteria :type units: dict {<type_id1> : <unit_criteria_for_type_id1>, <type_id2> : <unit_criteria_for_type_id2>} :param override_config: Additional configuration options to be accepted from user :type override_config: dict :return: applicability reports dictionary keyed by content type id :rtype: dict """ result = {} conduit = ProfilerConduit() consumer_query_manager = managers.consumer_query_manager() bind_manager = managers.consumer_bind_manager() # Process Repo Criteria if repo_criteria: # Get repo ids satisfied by specified repo criteria repo_query_manager = managers.repo_query_manager() repo_criteria_ids = [ r['id'] for r in repo_query_manager.find_by_criteria(repo_criteria) ] # if repo_criteria is specified and there are no repos satisfying the criteria, return empty result if not repo_criteria_ids: return result else: repo_criteria_ids = None # Process Consumer Criteria if consumer_criteria: # Get consumer ids satisfied by specified consumer criteria consumer_ids = [ c['id'] for c in consumer_query_manager.find_by_criteria( consumer_criteria) ] else: if repo_criteria_ids: # If repo_criteria is specified, get all the consumers bound to the repos # satisfied by repo_criteria bind_criteria = Criteria( filters={"repo_id": { "$in": repo_criteria_ids }}) consumer_ids = [ b['consumer_id'] for b in bind_manager.find_by_criteria(bind_criteria) ] # Remove duplicate consumer ids consumer_ids = list(set(consumer_ids)) else: # Get all consumer ids registered to the Pulp server consumer_ids = [ c['id'] for c in consumer_query_manager.find_all() ] # if there are no relevant consumers, return empty result if not consumer_ids: return result else: # Based on the consumers, get all the repos bound to the consumers in consideration # and find intersection of repo_criteria_ids and consumer_repo_ids bind_criteria = Criteria( filters={"consumer_id": { "$in": consumer_ids }}) consumer_repo_ids = [ b['repo_id'] for b in bind_manager.find_by_criteria(bind_criteria) ] if not repo_criteria_ids: repo_criteria_ids = list(set(consumer_repo_ids)) else: repo_criteria_ids = list( set(consumer_repo_ids) & set(repo_criteria_ids)) if not repo_criteria_ids: return result # Create a dictionary with consumer profile and repo_ids bound to the consumer keyed by consumer id consumer_profile_and_repo_ids = {} all_relevant_repo_ids = set() for consumer_id in consumer_ids: # Find repos bound to the consumer in consideration and find an intersection of bound repos to the # repos specified by repo_criteria consumer_bound_repo_ids = [ b['repo_id'] for b in bind_manager.find_by_consumer(consumer_id) ] consumer_bound_repo_ids = list(set(consumer_bound_repo_ids)) # If repo_criteria is not specified, use repos bound to the consumer, else take intersection # of repos specified in the criteria and repos bound to the consumer. if repo_criteria_ids is None: repo_ids = consumer_bound_repo_ids else: repo_ids = list( set(consumer_bound_repo_ids) & set(repo_criteria_ids)) if repo_ids: # Save all eligible repo ids to get relevant plugin unit keys when unit_criteria is not specified all_relevant_repo_ids = (all_relevant_repo_ids | set(repo_ids)) consumer_profile_and_repo_ids[consumer_id] = { 'repo_ids': repo_ids } consumer_profile_and_repo_ids[consumer_id][ 'profiled_consumer'] = self.__profiled_consumer( consumer_id) if not unit_criteria: return result # Call respective profiler api according to the unit type to check for applicability for unit_type_id, criteria in unit_criteria.items(): # Find a profiler for each type id and find units applicable using that profiler. profiler, cfg = self.__profiler(unit_type_id) call_config = PluginCallConfiguration( plugin_config=cfg, repo_plugin_config=None, override_config=override_config) try: report_list = profiler.find_applicable_units( consumer_profile_and_repo_ids, unit_type_id, criteria, call_config, conduit) except PulpExecutionException: report_list = None if report_list is None: _LOG.warn( "Profiler for unit type [%s] is not returning applicability reports" % unit_type_id) else: result[unit_type_id] = report_list return result
def test_bind_failed_on_consumer(self): # Setup self.populate() # Test options = {} itinerary = bind_itinerary( self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID, self.NOTIFY_AGENT, self.BINDING_CONFIG, options) call_reports = self.coordinator.execute_multiple_calls(itinerary) # Verify self.assertEqual(len(call_reports), 2) for call in call_reports: self.assertNotEqual(call.state, dispatch_constants.CALL_REJECTED_RESPONSE) # run task #1 (actual bind) self.run_next() # verify bind created manager = factory.consumer_bind_manager() binds = manager.find_by_consumer(self.CONSUMER_ID) self.assertEquals(len(binds), 1) bind = binds[0] self.assertEqual(bind['consumer_id'], self.CONSUMER_ID) self.assertEqual(bind['repo_id'], self.REPO_ID) self.assertEqual(bind['distributor_id'], self.DISTRIBUTOR_ID) self.assertEqual(bind['notify_agent'], self.NOTIFY_AGENT) self.assertEqual(bind['binding_config'], self.BINDING_CONFIG) # run task #2 (notify consumer) self.run_next() # verify pending consumer request (pending) request_id = call_reports[1].call_request_id bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind['consumer_actions'] self.assertEqual(len(actions), 1) self.assertEqual(actions[0]['id'], request_id) self.assertEqual(actions[0]['action'], Bind.Action.BIND) self.assertEqual(actions[0]['status'], Bind.Status.PENDING) self.assertTrue(isinstance(actions[0]['timestamp'], float)) # verify agent notified self.assertTrue(mock_agent.Consumer.bind.called) # simulated asynchronous task result report = DispatchReport() report.succeeded = False self.coordinator.complete_call_success(request_id, report.dict()) # verify pending consumer request (failed) manager = factory.consumer_bind_manager() bind = manager.get_bind(self.CONSUMER_ID, self.REPO_ID, self.DISTRIBUTOR_ID) actions = bind['consumer_actions'] self.assertEqual(len(actions), 1) self.assertEqual(actions[0]['id'], request_id) self.assertEqual(actions[0]['action'], Bind.Action.BIND) self.assertEqual(actions[0]['status'], Bind.Status.FAILED) self.assertTrue(isinstance(actions[0]['timestamp'], float))
import sys from optparse import OptionParser from pulp.server.auth.cert_generator import SerialNumber from pulp.server.db import connection from pulp.server.db.model.consumer import UnitProfile from pulp.server.exceptions import DuplicateResource, MissingResource from pulp.server.managers import factory from pulp_rpm.common.ids import TYPE_ID_RPM, TYPE_ID_DISTRIBUTOR_YUM SerialNumber.PATH = '/tmp/sn.dat' connection.initialize() factory.initialize() CONSUMER_MGR = factory.consumer_manager() PROFILE_MGR = factory.consumer_profile_manager() CONSUMER_BIND_MGR = factory.consumer_bind_manager() def get_consumer_id(prefix, index, count): value = str(index) desired_length = len(str(count)) num_length = len(value) needed_padding = desired_length - num_length for i in range(0, needed_padding): value = "0" + value return "%s_%s" % (prefix, value) def get_even_profile(): random_num = random.randint(0, 1000) profile = [{