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

feat(environments): allowed-times constraint #586

Merged
merged 1 commit into from
Nov 7, 2019

Conversation

asher
Copy link
Contributor

@asher asher commented Nov 6, 2019

No description provided.

| {
| "hours": "6-18",
| "days": "Monday-Friday",
| "tz": "America/Los_Angeles"
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the YAML payload have the tz field as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, we discussed this previously. It's necessary otherwise the server is going to interpret those hours as UTC

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's an optional field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also note the dynamic config to set a default TZ for windows other than UTC. This will be set to "America/Los_Angeles " so our users will only have to specify a TZ if they want something else.

@@ -6,6 +6,24 @@ import com.netflix.spinnaker.keel.api.DeliveryConfig

interface ConstraintEvaluator<T : Constraint> {

companion object {
inline fun <reified T> getConstraintForEnvironment(
Copy link
Contributor

Choose a reason for hiding this comment

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

Kotlin newbie question: does this need to be reified if you're not checking/using the type of T in the method?

Copy link
Contributor

Choose a reason for hiding this comment

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

Correct. Good spot.

Copy link
Contributor

Choose a reason for hiding this comment

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

Kotlin studies paying off! 😄

@JsonDeserialize(`as` = DependsOnConstraint::class)
sealed class Constraint
@JsonTypeInfo(
include = JsonTypeInfo.As.PROPERTY,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be EXISTING_PROPERTY

Comment on lines 12 to 13
Type(value = DependsOnConstraint::class, name = "depends-on"),
Type(value = TimeWindowConstraint::class, name = "time-window")
Copy link
Contributor

Choose a reason for hiding this comment

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

You should be able to omit the name since it's a constant in the types

| "windows": [
| {
| "hours": "6-18",
| "days": "Monday-Friday",
Copy link
Contributor

Choose a reason for hiding this comment

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

With this format can I specify something like "Monday, Wednesday and Friday"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or is that just 3 separate windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"and" isn't valid but you could specify "Monday, Wednesday, Friday"

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

Choose a reason for hiding this comment

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

@asher jinx.

| {
| "hours": "6-18",
| "days": "Monday-Friday",
| "tz": "America/Los_Angeles"
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, we discussed this previously. It's necessary otherwise the server is going to interpret those hours as UTC

Comment on lines 16 to 17
import java.util.TimeZone
import java.util.TimeZone.getTimeZone
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we use java.time.ZoneId here?

import java.util.TimeZone
import java.util.TimeZone.getTimeZone

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for taking the time to document the class so well. ❤️

.toString()
.toLowerCase()
val todayShort = now.dayOfWeek
.getDisplayName(TextStyle.SHORT, Locale.getDefault())
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be Locale.US for consistency with the formatters up above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's the other way around, they should be Locale.default. Thanks for pointing out the discrepancy.

Copy link
Member

@erikmunson erikmunson left a comment

Choose a reason for hiding this comment

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

💯 This is some serious awesomeness. Nice work!

I had a couple specific comments, but also a more general observation: the way the constraint syntax works right now, you can't tell from just reading it whether the time windows are inclusion windows (only deploy during these times) or exclusion windows (don't deploy during these times). You can try to make assumptions (e.g. "it's a constraint so it's probably asserting things to approve, not deny"), but there's definitely room for interpretation as-is.

I sort of think about these constraints like unit test assertions — in theory you should be able to read them and get a sense of what the plain-english intent is, when possible. Any thoughts on how we can make the expected behavior more clear? Perhaps naming the windows field something more descriptive?

* constraints:
* - type: time-window
* windows:
* - days: Monday,Tuesday-Friday
Copy link
Member

Choose a reason for hiding this comment

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

Up in a test file I saw all lowercase for this value — looks like in this evaluator we're coercing any capitalization into lowercase?

I don't have a problem with that necessarily, but might be good to shoot for a standardized way of doing examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or what if I add an additional window here that uses lower case? I want to show the flexibility in how days can be specified, but not sure how best to do that.

Copy link
Member

Choose a reason for hiding this comment

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

yeah maybe? I dunno. I suspect all these constraints will need really thorough docs to be successful. It's a detail and probably not super important tbh.

* hours: 11-16
* tz: America/Los_Angeles
* - days: Wed
* hours: 12-14
Copy link
Member

Choose a reason for hiding this comment

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

Any opinions on 24hr time vs 12pm-2pm? Latter seems a little more readable, but not sure whether it's "better" from an overall perspective.

Copy link
Contributor

Choose a reason for hiding this comment

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

next you'll want us to use Fahrenheit

Copy link
Member

Choose a reason for hiding this comment

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

Kelvin, obviously.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Am and pm, how very quaint.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm delighted with 24hour

Copy link
Member

Choose a reason for hiding this comment

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

Alright alright, if everybody prefers 24h then let’s go with that. I’m coming from the perspective that for really any UI we and other engineers use day to day, times get mapped to 12h because that’s typically easier to visually parse at a glance. I generally see these YAML configs as more similar in their UX needs to UIs vs. actual programming code.

?: shortDayFormatter.parseUnresolved(days[1], ParsePosition(0))
?: throw InvalidConstraintException("Failed parsing day range $this")

val intRange = IntRange(DayOfWeek.from(day1).value, DayOfWeek.from(day2).value)
Copy link
Contributor

Choose a reason for hiding this comment

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

What would happen here if you specify something backwards like thu-mon?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably not what the user would want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we throw an InvalidConstraintException in that case, then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the behavior so wrap around hours and days work as one would reasonably expect.

name = "prod",
constraints = setOf(constraint)
)
private val previousEnvironment = Environment(
Copy link
Contributor

Choose a reason for hiding this comment

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

Philosophical question for the group: I thought time-window would be a type of constraint/requirement that would gate deployment into an environment regardless of a promotion flow between environments (I remember discussing this with Michael for the MD blog post), but the function in the evaluator interface is called canPromote. Can this be used with a single environment?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should be able to control deployment into any environment with a time-window constraint. That applies if you have only 1 environment and you only want new builds deployed there on, let's say, weekdays.

I think that each constraint that is on an environment is evaluated, and they are && together. So if every constraint returns true to canPromote() then we would promote the artifact.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, Emily. I was just wondering if for the tests for this particular constraint (unlike for depends-on, which obviously needs 2 environments), we should then have a single environment in the fixtures.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll delete "staging" from the fixture if its causing confusion.

| - type: time-window
| windows:
| - hours: 6-18
| - days: mon-fri
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this yaml be:

windows:
- hours: 6-18
  days: mon-fri

(no dash by days) to mean that those go together as one window?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ooh yes, otherwise it will be 2 separate windows

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, I'm so bad at yaml!

@erikmunson
Copy link
Member

Another thought: the execution window UI for pipelines has three shortcut links that auto-select days for you: All, Weekends, and Weekdays.

Do any of those three seem like they'd be useful additions in the days field?

@asher
Copy link
Contributor Author

asher commented Nov 7, 2019

@erikmunson I'll add the weekdays/weekends shortcuts for days, good idea. (All is the default when days are omitted)

Copy link
Contributor

@luispollo luispollo left a comment

Choose a reason for hiding this comment

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

Other than the tiny things pointed out already, this looks great to me! Good stuff @asher!

@asher asher force-pushed the constraint branch 3 times, most recently from 81507a0 to 4f5b9f9 Compare November 7, 2019 17:42
@asher asher changed the title feat(environments): time-window constraint feat(environments): allowed-times constraint Nov 7, 2019
@asher asher added the ready to merge Approved and ready for merge label Nov 7, 2019
data class TimeWindow(
val days: String? = null,
val hours: String? = null
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think defining these here as strings means we get no validation when you submit a delivery-config. Do you think we could do some sort of validation like we do here, where that validation is pulled from the AllowedTimesConstraintEvaluator?

I'd like to know when I submit that I've spelled something wrong vs an error later :)

import java.lang.RuntimeException

class InvalidConstraintException(
message: String
Copy link
Contributor

@emjburns emjburns Nov 7, 2019

Choose a reason for hiding this comment

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

Probably should add constraint name here, so we know where the error is coming from?

class InvalidConstraintException(
  constraintName: String,
  message: String
) : RuntimeException("$constraintName: $message")

or something?

}
.failed()
.isA<InvalidConstraintException>()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like a test for when timezone is not set

* hours: 12-14
* tz: America/Los_Angeles
*/
@Component
Copy link
Contributor

Choose a reason for hiding this comment

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

This class is super clear. Thanks for adding great class docs, useful helper functions, keeping the core logic short and easy to reason about, and pulling the complex parsing out into smaller functions.

I struggled with this kind of calculation in Swabbie, and it's delightful to see something that reads well and makes sense here!

@asher asher merged commit 7f513a8 into spinnaker:master Nov 7, 2019
@asher asher deleted the constraint branch November 7, 2019 19:08
@mergify mergify bot added the auto merged label Nov 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto merged ready to merge Approved and ready for merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants