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

(MODULES-6845) Allow provider to read / modify V2 tasks #27

Merged
merged 8 commits into from
May 7, 2018

Conversation

Iristyle
Copy link
Contributor

No description provided.

@Iristyle Iristyle force-pushed the pr/26 branch 5 times, most recently from 4f50c60 to 502bc4b Compare April 23, 2018 23:54
@Iristyle Iristyle mentioned this pull request Apr 24, 2018
@Iristyle Iristyle force-pushed the pr/26 branch 2 times, most recently from 77dbb8b to 79c2135 Compare April 24, 2018 05:47
@Iristyle Iristyle changed the title Pr/26 (MODULES-6845) Allow provider to read / modify V2 tasks Apr 24, 2018
@Iristyle
Copy link
Contributor Author

Iristyle commented Apr 24, 2018

Copy link
Contributor

@glennsarti glennsarti left a comment

Choose a reason for hiding this comment

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

Not sure where this leaves the provider as it's no longer an adapter

@@ -28,7 +28,11 @@ def initialize(work_item = nil, trigger = nil)
def enum
@tasksched.enum_task_names(PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::ROOT_FOLDER,
include_child_folders: false,
include_compatibility: [PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V2]).map do |item|
include_compatibility: [
Copy link
Contributor

Choose a reason for hiding this comment

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

So now that we can show V2 tasks, does mean we should be enabling child folders, not just the root folder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah probably... not sure if that's worth a separate ticket or not.

@Iristyle Iristyle force-pushed the pr/26 branch 3 times, most recently from 7a5f725 to eeb5bc0 Compare April 25, 2018 06:25
@glennsarti
Copy link
Contributor

I have a few issues with this PR. The TaskScheduler2V1Task was meant to mimic the V1 provider so we maintained backwards compatibility. This PR breaks that contract as it now returns V2, V1 and AT tasks for enum. This is also borne out in the spec tests where it runs the sames tests over the provider for both the original implementation and the V2 adapter.

And because that contract is broken I expect spec tests to be failing.

I would've thought it would make more sense to;

  1. Copy the contents from scheduled_task/taskscheduler_api2.rb into win32_taskscheduler.rb as we know this is compatible

  2. Copy the spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb file to spec/unit/puppet/provider/scheduled_task/taskscheduler_api2_spec.rb

  3. Remove references to PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task from spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb

  4. Remove references to Win32::TaskScheduler from spec/unit/puppet/provider/scheduled_task/taskscheduler_api2_spec.rb

This means now the V1 style provider still uses the V2 API for it's underlying work and can be used for backwards compatiblity and that the V2 provider can
be developed further with more "stuff"

@Iristyle
Copy link
Contributor Author

Thanks for the feedback @glennsarti - I'm still working on a more comprehensive explanation of the thought process here, because it's a little complicated.

In the interim, here are a few bits about where we were coming from:

  • Don't change win32_taskscheduler at all, so that we completely maintain backwards compat against V1 APIs / be more risk averse. This is the part where we have diverged the most in ideas, as you want to keep V1 compat by using the V2 APIs instead because you're concerned V1 APIs shouldn't be used at all. I'm not convinced they've gone anywhere or they can't be used, but then there's this post on SO which seems to make that case. We were coming from the angle that we know this provider will be yanked at some point in the not-too-distant-future, so it didn't seem like it made sense to make changes to it. That said, I'm not wed to this at all - just want to understand tradeoffs fully for either approach.

  • Our ideas around contracts are different. From my perspective our only contract to end users is via the attributes on the type, and we can't change those in a way that breaks backwards compat regardless. What is returned / queried should be immaterial as long as it conforms to the same basic structure (if we rip apart tests, I suspect this will be harder to enforce - having tests shared amongst providers helps us prove this). There is no real contract between type and provider aside from the generic T&P contract for all Puppet providers and a couple of methods on the provider. Beyond that, it's all internal details. (I'm glossing over the details a bit because there are some subtle contracts like class of Exception, structures within the trigger hash, etc - but my statement mostly holds true)

  • I think the taskscheduler_api2 provider has to return all compatibility levels - otherwise users will not be able to manage existing V1 tasks with the new default provider (this is one of the other reasons I didn't see the merit in redoing win32_taskscheduler). I think this is one of the only bits that is non-negotiable - just want to make sure we're on the same page.

  • Now that Trigger code has been pulled out, it shows that there is almost nothing different between TaskSchedulerV1Task and TaskScheduler2Task. It makes us wonder why we need both, and it seemed nice if we could ditch one. But if we do the win32_taskscheduler rejiggering, we'll obviously need the adapter or something like it to remain (it might be inlinable into the provider)

  • We want to completely trash the V1 internal representation as it can't roundtrip things properly, and is an unnecessary conversion (haven't filed ticket for that yet)

  • @michaeltlombardi added code today to constraint provider specific attributes like compatibility which will come in handy later - (MODULES-6526) Confine compatibility to a feature #30

Anyhow - this was quick off the head stuff - let's chat in person soon.

@glennsarti
Copy link
Contributor

Yeah I think we're mostly on the same page. Just need to define the direction of the older win32_taskscheduler.rb file as that determines some other things.

Don't change win32_taskscheduler at all, so that we completely maintain backwards compat against V1 APIs / be more risk averse. This is the part where we have diverged the most in ideas, as you want to keep V1 compat by using the V2 APIs instead because you're concerned V1 APIs shouldn't be used at all.

In addition to that point, I would like to ditch the V1 API calls and use the V2 API calls instead because;

Pros:

  • V2 API is easier to reason about, maintain and debug
  • We can take advantage of the all the trigger refactor that we've done
  • We've done a lot of work to make the V2API <-> V1 Task adapter, and we know it works, and have tests to prove it. So it's low risk to use it instead of the V1 APIs
  • We have to always carry the win32_taskscheduler.rb file otherwise the puppet-agent version will be loaded, so why not use a "better" version of it
  • We can still "code freeze" the V1 Task adapter code, as we would, if we used the V1 API call style.

Cons:

  • There is a risk that the Task adapter is not 100% the same as the original code. Personally I feel the risk is WAY lower than the risk/effort to debug/fix/maintain the V1 API calls.
  • Requires work to inline the V1Task class into the V1 provider

Our ideas around contracts are different. ...

More than likely. I used an overloaded word.

I think the taskscheduler_api2 provider has to return all compatibility levels - otherwise users will not be able to manage existing V1 tasks with the new default provider

100% agree

Now that Trigger code has been pulled out, it shows that there is almost nothing different between TaskSchedulerV1Task and TaskScheduler2Task. It makes us wonder why we need both, and it seemed nice if we could ditch one. But if we do the win32_taskscheduler rejiggering, we'll obviously need the adapter or something like it to remain (it might be inlinable into the provider)

In my mind the fact that they're almost the same right now is expected. All we've really done so far is all "tech debt"/backend work. Now that that's all been done we're adding features and this where they'll being to diverge.

We want to completely trash the V1 internal representation as it can't roundtrip things properly ...

I assume you're talking V1 API here

@michaeltlombardi added code today to constraint provider specific attributes like compatibility which will come in handy later

Saw that. +1

... let's chat in person soon.

Agreed!

@Iristyle
Copy link
Contributor Author

Iristyle commented Apr 27, 2018

In addition to that point, I would like to ditch the V1 API calls and use the V2 API calls instead because;

Pros:

  • V2 API is easier to reason about, maintain and debug

I think the APIs are about the same after the Trigger code was refactored out. However, it wasn't factored out of the existing win32_taskscheduler, so that is certainly more difficult to maintain. I do think it's easier to work on top of one set of APIs rather than two.

We can take advantage of the all the trigger refactor that we've done

+1

  • We've done a lot of work to make the V2API <-> V1 Task adapter, and we know it works, and have tests to prove it. So it's low risk to use it instead of the V1 APIs

It turns out that most of the work here is in the Trigger nonsense, which caused the adapter to go on a 300-400 line diet. But I agree it would be nice to leverage the work invested in the adapter.

  • We have to always carry the win32_taskscheduler.rb file otherwise the puppet-agent version will be loaded, so why not use a "better" version of it

Yes, we will have to carry this until the point at which Puppet itself trashes it to make sure that it is not the default provider.

We can still "code freeze" the V1 Task adapter code, as we would, if we used the V1 API call style.
Cons:

There is a risk that the Task adapter is not 100% the same as the original code. Personally I feel the risk is WAY lower than the risk/effort to debug/fix/maintain the V1 API calls.

My thinking here is that we don't touch it either way anyhow. If a user has a problem with what's "in the box" for Puppet, then they must install the module, at which point they get a new default provider. Any "fixes" they need go against api2. A user would have to have the module and opt in to the old provider to use that code path, which seems unlikely, and hopefully never happens.

With any luck, we never touch win32_taskscheduler provider again.

  • Requires work to inline the V1Task class into the V1 provider

That was just me thinking about further into the future. Not something that necessarily needs to be done.

Our ideas around contracts are different. ...

More than likely. I used an overloaded word.

The methods on the provider that the type calls are validate_trigger, trigger_inysnc? and user_insync?. As long as we have those (in addition to the other things I mentioned), we at least have the same API. That said, now that we know the win32_taskscheduler in this module overrides one in core, I think we have more latitude to make changes there as well.

Now that Trigger code has been pulled out, it shows that there is almost nothing different between TaskSchedulerV1Task and TaskScheduler2Task. It makes us wonder why we need both, and it seemed nice if we could ditch one. But if we do the win32_taskscheduler rejiggering, we'll obviously need the adapter or something like it to remain (it might be inlinable into the provider)

In my mind the fact that they're almost the same right now is expected. All we've really done so far is all "tech debt"/backend work. Now that that's all been done we're adding features and this where they'll being to diverge.

We're not sure yet, but we suspect much of the divergent behavior is going to end up in the factored out Trigger helpers, rather than in the providers... which is what had us thinking about this. We can always revisit at a later date when we do some additional cleanup.

We want to completely trash the V1 internal representation as it can't roundtrip things properly ...

I assume you're talking V1 API here

Not ... exactly.

I mean some of the Trigger helper code and our "internal representation" of triggers. Right now, we do some things that were appropriate at initial implementation, but have become clearly unnecessary now after refactoring.

  • As a provider fetches instances, it calls into the adapter
  • The adapter reads ITrigger instances (V2 API), converting them to the V1-esque internal representation using Trigger::V1.from_iTrigger (to remain consistent with how they're returned from the old win32_taskscheduler)
    • V1-esque because that structure still has to be mapped to the FFI struct and vice versa (see Puppet core code for populate_trigger and populate_hash_from_trigger)
    • For saving through V2 API, we use append_v1trigger, translating what was at first a V2 ITrigger (currently a V1-esque struct) back to a V2 ITrigger
  • The provider immediately translates those to the manifest hash structure in trigger

For ingress manifest hash definitions, provider.trigger_inysnc? is called from the type to do a comparison, which funnels the heavy-lifting to triggers_same? which compares some manifest hash fields directly, but additionally converts them to temporary copies of the V1-esque representation to compare as well.

I would like to see the V1-esque representation removed completely (in addition to cleaning up comparison and prefetch logic), which we've now realized cannot be done without migrating win32_taskscheduler provider to V2 APIs. IMHO, that's the most compelling reason to get rid of a provider built on the old APIs, because even if new provider doesn't use V1-esque hashes as an intermediate representation, we won't be able to remove the extra Trigger conversion code as the old provider would still need it.

So to summarize, I think we're going to migrate the code from api2 into win32_taskscheduler as you've suggested, and will put up a PR as such. I still think that tests are going to have to overlap to ensure the old manifest hash structures still flow through the new provider properly. But I think we can tackle that in more detail later. We still want to make sure that the new provider is a superset of the old provider (even though we plan to eventually rip the old one out).

michaeltlombardi and others added 3 commits May 2, 2018 14:13
Prior to this commit the spec for the providers iterated
over the helpers used.  This causes some problems with
plans to copy the new provider into the old as both
providers will temporarily use the same provider.

This commit changes the spec logic to iterate over the
providers and map the appropriate helpers to them. No
tests are changed or affected.
Prior to this commit the code relied on the Win32::TaskScheduler class
to provide the DISABLED constant, indicating a task is disabled. This
required loading that class and caused puppet resource calls to fail.

This commit adds the task flag constants to the `TaskScheduler2` class
and bypasses the need to load the Win32::TaskScheduler at all. It then
replaces all calls for that constant to the appropriate call to those
defined in `TaskScheduler2`.
@Iristyle
Copy link
Contributor Author

Iristyle commented May 3, 2018

@michaeltlombardi I resumed your PR at #34 here... however, there are a few things not working right (as of 2nd to last commit 0a0228c).

  • ensure => absent is not removing my test task
  • Get message like Notice: /Stage[main]/Main/Scheduled_task[test]/ensure: created when trying to modify existing task
  • idempotency is busted... I get a message about task compat level change, which I'll need to look at

I suspect this may have to do with the .job extension, but not sure yet.

I've been messing around with this basic manifest and puppet apply

scheduled_task { 'test':
  ensure        => 'present',
  command       => 'c:\\windows\\system32\\notepad.exe',
  # compatibility => 2,
  enabled       => 'true',
  trigger       => [
  {
    'schedule' => 'once',
    # 'start_date' => '1899-12-30',
    'start_date' => '2018-04-30',
    'start_time' => '00:00',
    'minutes_interval' => 0,
    'minutes_duration' => 0,
  }],
  # provider => 'win32_taskscheduler'
}

@michaeltlombardi
Copy link
Contributor

RE: Compatibility, as you discovered, it's because the V2 helper creates the task with a compat level of 2. Unless the compat level is specified, however, it will try to default back to 1, preventing idempotency. This means we created a situation in which not specifying compatibility at all will create an initially non-idempotent task, although this will resolved after two runs.

To fix this, we'll need to have it to be able to pass through the compat level to new_work_item, updating the taskscheduler_api2 provider's create call as well.

  • create in taskscheduler_api2 will need to pass the compatibility level
  • initialize in taskscheduler2_task will need to accept the compatibility level and send it on to new_work_item
  • new_work_item in taskscheduler2_task will need to accept the compatibility level and send it on to set_compatibility instead of hard-coding passing V2 compatibility.

@Iristyle
Copy link
Contributor Author

Iristyle commented May 3, 2018

@michaeltlombardi / @glennsarti ready for another review - almost there, but not quite.

 - Previously, specs were hardcoded to use the Win32::TaskScheduler
   helper during some retrieval and creation style tests, when they
   should be switching the class based on the appropriate helper,
   either PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task OR
   Win32::TaskScheduler.

   This creates a minor discrepancy in a subsequent commit when
   additional testing is added for compatibility, which is implemented
   only in TaskScheduler2V1Task and *not* Win32::TaskScheduler
 - The initial V2 implementation of compatbility in the V2 provider
   did not properly use the attribute to set the compatibility level
   when constructing new task instances.

   Remove the hardcoded setter that pushes through V2 and instead
   call the property setter for `compatibility` the same way as is
   done for other providers.

 - Add additional validations through tests to demonstrate this
   behavior, noting the old Win32_TaskScheduler provider does not
   follow this same behavior.
Prior to this commit the Win32 provider in this module was a near
mirror of the existing provider in core Puppet.  In order to more
easily maintain the new module, to debug it, and to onboard team
members to maintaining it, this commit copies over the code from
the new provider into the old one. As the new provider was made
to keep in parity with the old one and the tests already existed
to verify this, the change is determined to be low risk.

In a future commit the V2 provider will be updated to manage
scheduled tasks of all compatibility modes and will be the code
which gets all new features and improvements. The legacy provider
will be frozen at a point in the near future for all changes
except for security and critical bug fixes.

This commit also updates the test suite to verify the changes and
removes unneccessary logic from the tests which were needed when
the providers did not share helper code.

However, in this iteration, the tests only run over the legacy
provider and not the V2 provider, though they are using the same
helper at this time.  In a future commit, when the V2 provider is
updated to use the new V2 helper, the test suite will again run
for both.

Note also that references to the compatibility property were removed
from the win32_taskscheduler code because only the taskscheduler_api2
supports it.
michaeltlombardi and others added 2 commits May 4, 2018 15:16
 - Prior to this commit both the win32 (legacy) and api2 (new)
   providers leveraged the adapter code in `TaskScheduler2V1Task`
   to manage scheduled tasks.  This commit updates the API2
   provider to use the TaskScheduler2Task helper, allowing us to
   freeze work on the adapter code and begin to add new features
   to the API2 helper.

   At this point in time, only the helper has been swapped out,
   the provider itself has not been meaningfully altered.

 - Make sure the helper queries for all task compatibility levels
   including 3 (2008R2), 4 (2012R2 / 2016), 6 (Windows 10)
   in addition to just V1 and AT as the old provider did.

   This ensures that `puppet resource` returns all tasks on the system
   even if they cannot be configured yet.
 - To fully support reading / writing v2 tasks requires allowing the
   compatibility value in the scheduled_task type to be anything
   valid, including 1, 2, 3, 4 and 6.

   1 - 2003, XP, 2000
   2 - Vista / 2008
   3 - 7, 2008R2
   4 - 8, 2012R2, 2016
   6 - 10
@Iristyle
Copy link
Contributor Author

Iristyle commented May 4, 2018

@glennsarti we went through each commit individually and are satisfied with how this seems to be working (including in-place upgrading / downgrading compatibility level)

Want to give this one last pass and merge?

Thanks!

Copy link
Contributor

@glennsarti glennsarti left a comment

Choose a reason for hiding this comment

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

LGTM

@glennsarti glennsarti merged commit b0c62c8 into puppetlabs:master May 7, 2018
@Iristyle Iristyle deleted the pr/26 branch May 7, 2018 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants