Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create initial tower objects when we start the worker #14283

Merged

Conversation

carbonin
Copy link
Member

@carbonin carbonin commented Mar 10, 2017

These objects will be used for dynamically creating things like job templates during playbook runs from automate.

Requires #14377 for storing the ansible side ids with the provider.

https://www.pivotaltracker.com/story/show/140098929

@carbonin
Copy link
Member Author

All, marked as WIP until you all sign off on where this kind of stuff belongs. I tested out the logic on an appliance so I really just need opinions on where this should live. (Of course if the logic is wrong, please tell me that too 😜 )

@blomquisg from a provider side view, is this where this kind of thing belongs? I don't know what the responsibilities of the provider/manager typically are so I added it here just because we already had a subclass for embedded ansible for the provider class.

@Fryguy I'm not sure I would want to add the other seeding stuff here. Maybe something in the worker for that as it's more system/filesystem related?

@gmcculloug Is this about what you guys need to create the job templates for the service runs? The provider instance will be accessible to you guys when you need this info I hope?

@carbonin
Copy link
Member Author

Ping @jrafanie
This is the bit that will break if Apache isn't up and running when the worker starts. We may want to figure that out before merging this ...

end

def default_organization(connection = connect)
@@default_org ||= connection.api.organizations.all(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you are using a class variable. In fact, given that those are inherited, I think that's generally bad right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I don't want it to be an instance variable because I want to cache more aggressively than that. I don't have much experience with class instance variables, but it feels like I would need to write explicit accessors to use them from an instance context, right? Plus it's all a bit more complicated because this is a mixin ...

I'm not crazy about any of the options, what do you think?

@Fryguy
Copy link
Member

Fryguy commented Mar 10, 2017

Not a fan of the term "Sandbox" in these objects. Sandbox implies things you can play with and wreck. This is more of a "ManageIQ default"...closer to seeding.

@Fryguy
Copy link
Member

Fryguy commented Mar 10, 2017

When we discussed yesterday I thought this would live as part of the embeddedansible worker during its do_before_work_loop phase. Did that not work as expected?

@carbonin
Copy link
Member Author

@Fryguy From a timing perspective, yes, it will be called from around there, but I'm not sold on where to put the code.

I felt like it would be a bit strange for the automate methods to call out to a worker class or instance to get the default values so I put everything on the provider.

Like I said, I think the rest of the plugin import stuff should belong with the worker, but I'm a bit split on this part.

@gmcculloug
Copy link
Member

@bzwei @bdunne Please review

@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch from b1ec5f2 to 43e07a9 Compare March 15, 2017 15:20

def default_organization(connection = connect)
@@default_org ||= connection.api.organizations.all(
:name => product_name
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a reliable way to identify these objects? If the product_name changes we will not be able to find our defaults.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is a case for adding some kind of "system" identifier @Fryguy ? Then we can just look them up that way after creating them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second (third?) thought a flag won't really help, not for the organization at least, because we don't collect inventory for the organization.

@carbonin
Copy link
Member Author

This is the bit that will break if Apache isn't up and running when the worker starts. We may want to figure that out before merging this ...

Turns out it wasn't that complicated #14353 😅

@@ -58,8 +58,85 @@ def url=(new_url)
default_endpoint.url = uri.to_s
end

def create_default_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be in shared I don't think

@carbonin
Copy link
Member Author

Okay, spoke with @Fryguy and we're taking a bit of a new approach.

We're going to store these in custom attributes related to the provider. That way we don't have to rely on our modeling and potentially conflict with the refresher by adding fields (like a system flag or something) to the AR instances and we also don't have to constantly connect to the provider and match names to find things.

The custom attribute setter/getters will live in a concern and they will be used by the EmbeddedAnsibleWorker (for setting) and automate will use the getters when they need to create stuff referencing these objects.

We also won't need a migration, so that's good 😄

@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch 2 times, most recently from 61d5029 to a03e434 Compare March 16, 2017 22:02
@carbonin
Copy link
Member Author

I split out the provider side stuff into a separate PR here #14377

I'm going to make this one specific to the EmbeddedAnsibleWorker stuff.

@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch from a03e434 to bbd2c6b Compare March 17, 2017 12:54
@carbonin carbonin requested review from jrafanie and removed request for blomquisg and gmcculloug March 17, 2017 12:55
@carbonin carbonin assigned gtanzillo and unassigned blomquisg Mar 17, 2017
def ensure_host(provider, connection)
return if provider.default_host

provider.default_host = connection.api.hosts.create!(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carbonin make sure the created localhost has variable ansible_connection: local

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bzwei Is that in the variables yaml string?

I'm guessing that would be :variables => {'ansible_connection' => "local"}.to_yaml for the API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be addressed now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch from bbd2c6b to 46b5018 Compare March 17, 2017 15:55
@miq-bot
Copy link
Member

miq-bot commented Mar 17, 2017

This pull request is not mergeable. Please rebase and repush.

@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch from 1d646c1 to f9ec890 Compare March 17, 2017 19:46
@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch from f9ec890 to b67beca Compare March 17, 2017 20:33
def ensure_organization(provider, connection)
return if provider.default_organization

provider.default_organization = connection.api.organizations.create!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this even work? default_orgainzation= creates a CustomAttribute on the Provider right? But you're passing in a AnsibleTowerClient::Organization instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -36,6 +38,17 @@ def setup_ansible
_log.info("calling EmbeddedAnsible.start")
EmbeddedAnsible.start
_log.info("calling EmbeddedAnsible.start finished")

5.times do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 5 seconds long enough for the server to start up and respond?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EmbeddedAnsible.start is synchronous so this felt unnecessary to me, but this solved the 502 errors I was getting when testing on an appliance.

We can also make the WAIT_FOR_ANSIBLE_SLEEP a worker setting, but I like to lean towards fewer knobs where I can 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, it returned to you but it wasn't ready yet? Can we try to hit it using curl or something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we try to hit it using curl or something?

That's essentially what EmbeddedAnsible.alive? does.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to have start do the sleep until alive? so start doesn't return when it's not yet alive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we could do that @jrafanie 👍


5.times do
if EmbeddedAnsible.alive?
break
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler if this acted like guard clause?

5.times do
  break if EmbeddedAnsible.alive?

  _log.info("Waiting for EmbeddedAnsible to respond")
  sleep WAIT_FOR_ANSIBLE_SLEEP
end

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup thanks.

@@ -10,7 +10,9 @@
let(:runner) {
worker
allow_any_instance_of(described_class).to receive(:worker_initialization)
described_class.new(:guid => worker_guid)
r = described_class.new(:guid => worker_guid)
allow(r).to receive(:worker).and_return(worker)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'll look into this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😢 I have to finish fixing this. So much code now depends on all of this junk running when you call .new though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I tried to do a bit to get rid of this and it turns out we have to jump through hoops because sync_config changes the server's zone to the one called "default" which ends up being nil unless we Zone.seed ...

Even then there is no way to deal with how the runner looks up the worker, I would still need to stub the .worker method (attribute) to make sure we don't actually call #remove_demo_data and #ensure_initial_objects.

So then the last option would be to get rid of the allow_any_instance_of and also stub the #worker method ... That makes the test that asserts that we change the zone of the provider fail because somehow we are getting this "default" zone in there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer here will be to just remove the zone/role stuff from settings. Then we don't have to deal with multiple sources of information for this type of thing.

Also, I don't really want to do that in this PR. I'll work on that as a follow up and fix this up there.

context "ObjectManagement concern" do
let(:provider) { FactoryGirl.create(:provider_embedded_ansible) }
let(:api_connection) do
conn = double("AnsibleAPIConnection")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of strange to stub #api to return the connection object itself. This could be:

let(:api_connection) { double("AnsibleAPIConnection", :api => tower_api) }
let(:tower_api)      { double("TowerAPI") }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, now that I read further...

let(:tower_api)      { double("TowerAPI", :organizations => org_collection, .....) }
let(:org_collection)       { double("AnsibleOrgCollection", :all => [org_resource]) }

Then you can get rid of the before block with all of the allows.


context "DefaultAnsibleObjects concern" do
before do
subject.save
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not FactoryGirl.create rather than adding the subject.save in the before block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think this was a rebase issue. I started this branch before #14383 was merged so instead of changing the subject for all the tests, I saved the record in just my context, during the rebase I think this line got left here.

I'll refactor in a new commit so that the individual specs define the subject rather than the shared example.

Thanks for catching this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'll make this change in #14377

…bleWorker::Runner

This creates a concern around creating and removing objects
in the embedded ansible instance.

In particular we remove all of the demo data that is initialized
for us during setup and we create just the objects that we need
and save the ids to the provider.
Without this we were we were getting "502 Bad Gateway" errors when
trying to remove the demo data through API calls to the embedded
ansible instance.  This was because even though the setup playbook
finished, the server would take just a bit longer to start to serve
requests.
@carbonin carbonin force-pushed the add_embedded_tower_initial_objects branch from f86348f to 5d2d5df Compare March 20, 2017 21:49
@carbonin carbonin changed the title [WIP] Create initial tower objects when we start the worker Create initial tower objects when we start the worker Mar 20, 2017
@carbonin carbonin removed the wip label Mar 20, 2017
@miq-bot
Copy link
Member

miq-bot commented Mar 20, 2017

Checked commits carbonin/manageiq@3484703~...5d2d5df with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
7 files checked, 0 offenses detected
Everything looks good. ⭐

@carbonin
Copy link
Member Author

@bdunne @jrafanie @Fryguy #14377 is merged so I think this one is ready for review.

@gmcculloug
Copy link
Member

@bdunne @jrafanie @Fryguy Any comments? Would like to get this merged if it is ready as there are followup changes required to reference these new elements.

Specifically I am aware of this one: https://github.com/ManageIQ/manageiq/blob/master/app/models/service_template_ansible_playbook.rb#L94
cc @bzwei


_log.info("Waiting for EmbeddedAnsible to respond")
sleep WAIT_FOR_ANSIBLE_SLEEP
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this better. Now the caller can be guaranteed to be alive or get an exception. 👍

Copy link
Member

@bdunne bdunne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM

let(:job_templ_resource) { double("AnsibleJobTemplResource", :id => 16) }
let(:proj_resource) { double("AnsibleProjResource", :id => 17) }

describe "#ensure_initial_objects" do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of the describe with only one it inside, but don't change just because of that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I agree for contexts, but I always use a describe for the method under test regardless of how many tests I'm writing.

@gmcculloug gmcculloug merged commit 5e631c1 into ManageIQ:master Mar 22, 2017
@gmcculloug gmcculloug added this to the Sprint 57 Ending Mar 27, 2017 milestone Mar 22, 2017
@carbonin carbonin deleted the add_embedded_tower_initial_objects branch May 18, 2017 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants