github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/operation/state_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package operation_test 5 6 import ( 7 "github.com/juju/charm/v12/hooks" 8 "github.com/juju/errors" 9 jc "github.com/juju/testing/checkers" 10 "go.uber.org/mock/gomock" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/yaml.v2" 13 14 "github.com/juju/juju/rpc/params" 15 "github.com/juju/juju/worker/uniter/hook" 16 "github.com/juju/juju/worker/uniter/operation" 17 "github.com/juju/juju/worker/uniter/operation/mocks" 18 ) 19 20 type StateOpsSuite struct { 21 mockStateRW *mocks.MockUnitStateReadWriter 22 } 23 24 var _ = gc.Suite(&StateOpsSuite{}) 25 26 var stcurl = "ch:quantal/application-name-123" 27 var relhook = &hook.Info{ 28 Kind: hooks.RelationJoined, 29 RemoteUnit: "some-thing/123", 30 RemoteApplication: "some-thing", 31 } 32 33 type stateTest struct { 34 description string 35 st operation.State 36 err string 37 } 38 39 var stateTests = []stateTest{ 40 // Invalid op/step. 41 { 42 description: "unknown operation kind", 43 st: operation.State{Kind: operation.Kind("bloviate")}, 44 err: `unknown operation "bloviate"`, 45 }, { 46 description: "unknown operation step", 47 st: operation.State{ 48 Kind: operation.Continue, 49 Step: operation.Step("dudelike"), 50 }, 51 err: `unknown operation step "dudelike"`, 52 }, 53 // Install operation. 54 { 55 description: "mismatched operation and hook", 56 st: operation.State{ 57 Kind: operation.Install, 58 Installed: true, 59 Step: operation.Pending, 60 CharmURL: stcurl, 61 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 62 }, 63 err: `unexpected hook info with Kind Install`, 64 }, { 65 description: "missing charm URL", 66 st: operation.State{ 67 Kind: operation.Install, 68 Step: operation.Pending, 69 }, 70 err: `missing charm URL`, 71 }, { 72 description: "install with action-id", 73 st: operation.State{ 74 Kind: operation.Install, 75 Step: operation.Pending, 76 CharmURL: stcurl, 77 ActionId: &someActionId, 78 }, 79 err: `unexpected action id`, 80 }, { 81 description: "install with charm url", 82 st: operation.State{ 83 Kind: operation.Install, 84 Step: operation.Pending, 85 CharmURL: stcurl, 86 }, 87 }, 88 // RunAction operation. 89 { 90 description: "run action without action id", 91 st: operation.State{ 92 Kind: operation.RunAction, 93 Step: operation.Pending, 94 }, 95 err: `missing action id`, 96 }, { 97 description: "run action with spurious charmURL", 98 st: operation.State{ 99 Kind: operation.RunAction, 100 Step: operation.Pending, 101 ActionId: &someActionId, 102 CharmURL: stcurl, 103 }, 104 err: `unexpected charm URL`, 105 }, { 106 description: "run action with proper action id", 107 st: operation.State{ 108 Kind: operation.RunAction, 109 Step: operation.Pending, 110 ActionId: &someActionId, 111 }, 112 }, 113 // RunHook operation. 114 { 115 description: "run-hook with unknown hook", 116 st: operation.State{ 117 Kind: operation.RunHook, 118 Step: operation.Pending, 119 Hook: &hook.Info{Kind: hooks.Kind("machine-exploded")}, 120 }, 121 err: `unknown hook kind "machine-exploded"`, 122 }, { 123 description: "run-hook without remote unit", 124 st: operation.State{ 125 Kind: operation.RunHook, 126 Step: operation.Pending, 127 Hook: &hook.Info{Kind: hooks.RelationJoined}, 128 }, 129 err: `"relation-joined" hook requires a remote unit`, 130 }, { 131 description: "run-hook relation-joined without remote application", 132 st: operation.State{ 133 Kind: operation.RunHook, 134 Step: operation.Pending, 135 Hook: &hook.Info{ 136 Kind: hooks.RelationJoined, 137 RemoteUnit: "some-thing/0", 138 }, 139 }, 140 err: `"relation-joined" hook has a remote unit but no application`, 141 }, { 142 description: "run-hook relation-changed without remote application", 143 st: operation.State{ 144 Kind: operation.RunHook, 145 Step: operation.Pending, 146 Hook: &hook.Info{ 147 Kind: hooks.RelationChanged, 148 RemoteUnit: "some-thing/0", 149 }, 150 }, 151 err: `"relation-changed" hook has a remote unit but no application`, 152 }, { 153 description: "run-hook with actionId", 154 st: operation.State{ 155 Kind: operation.RunHook, 156 Step: operation.Pending, 157 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 158 ActionId: &someActionId, 159 }, 160 err: `unexpected action id`, 161 }, { 162 description: "run-hook with charm URL", 163 st: operation.State{ 164 Kind: operation.RunHook, 165 Step: operation.Pending, 166 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 167 CharmURL: stcurl, 168 }, 169 err: `unexpected charm URL`, 170 }, { 171 description: "run-hook config-changed", 172 st: operation.State{ 173 Kind: operation.RunHook, 174 Step: operation.Pending, 175 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 176 }, 177 }, { 178 description: "run-hook relation-joined", 179 st: operation.State{ 180 Kind: operation.RunHook, 181 Step: operation.Pending, 182 Hook: relhook, 183 }, 184 }, 185 // Upgrade operation. 186 { 187 description: "upgrade without charmURL", 188 st: operation.State{ 189 Kind: operation.Upgrade, 190 Step: operation.Pending, 191 }, 192 err: `missing charm URL`, 193 }, { 194 description: "upgrade with actionID", 195 st: operation.State{ 196 Kind: operation.Upgrade, 197 Step: operation.Pending, 198 CharmURL: stcurl, 199 ActionId: &someActionId, 200 }, 201 err: `unexpected action id`, 202 }, { 203 description: "upgrade operation", 204 st: operation.State{ 205 Kind: operation.Upgrade, 206 Step: operation.Pending, 207 CharmURL: stcurl, 208 }, 209 }, { 210 description: "upgrade operation with a relation hook (?)", 211 st: operation.State{ 212 Kind: operation.Upgrade, 213 Step: operation.Pending, 214 Hook: relhook, 215 CharmURL: stcurl, 216 }, 217 }, 218 // Continue operation. 219 { 220 description: "continue operation with charmURL", 221 st: operation.State{ 222 Kind: operation.Continue, 223 Step: operation.Pending, 224 CharmURL: stcurl, 225 }, 226 err: `unexpected charm URL`, 227 }, { 228 description: "continue operation with actionID", 229 st: operation.State{ 230 Kind: operation.Continue, 231 Step: operation.Pending, 232 ActionId: &someActionId, 233 }, 234 err: `unexpected action id`, 235 }, { 236 description: "continue operation", 237 st: operation.State{ 238 Kind: operation.Continue, 239 Step: operation.Pending, 240 Leader: true, 241 }, 242 }, 243 } 244 245 func (s *StateOpsSuite) TestStates(c *gc.C) { 246 for i, t := range stateTests { 247 c.Logf("test %d: %s", i, t.description) 248 s.runTest(c, t) 249 } 250 } 251 252 func (s *StateOpsSuite) runTest(c *gc.C, t stateTest) { 253 defer s.setupMocks(c).Finish() 254 ops := operation.NewStateOps(s.mockStateRW) 255 _, err := ops.Read() 256 c.Assert(err, gc.Equals, operation.ErrNoSavedState) 257 258 if t.err == "" { 259 s.expectSetState(c, t.st, t.err) 260 } 261 err = ops.Write(&t.st) 262 if t.err == "" { 263 c.Assert(err, jc.ErrorIsNil) 264 } else { 265 c.Assert(err, gc.ErrorMatches, "invalid operation state: "+t.err) 266 s.expectState(c, t.st) 267 _, err = ops.Read() 268 c.Assert(err, gc.ErrorMatches, `validation of uniter state: invalid operation state: `+t.err) 269 return 270 } 271 s.expectState(c, t.st) 272 st, err := ops.Read() 273 c.Assert(err, jc.ErrorIsNil) 274 c.Assert(st, jc.DeepEquals, &t.st) 275 } 276 277 func (s *StateOpsSuite) setupMocks(c *gc.C) *gomock.Controller { 278 ctlr := gomock.NewController(c) 279 s.mockStateRW = mocks.NewMockUnitStateReadWriter(ctlr) 280 281 mExp := s.mockStateRW.EXPECT() 282 mExp.State().Return(params.UnitStateResult{}, nil) 283 return ctlr 284 } 285 286 func (s *StateOpsSuite) expectSetState(c *gc.C, st operation.State, errStr string) { 287 data, err := yaml.Marshal(st) 288 c.Assert(err, jc.ErrorIsNil) 289 strUniterState := string(data) 290 if errStr != "" { 291 err = errors.New(`validation of uniter state: invalid operation state: ` + errStr) 292 } 293 294 mExp := s.mockStateRW.EXPECT() 295 mExp.SetState(unitStateMatcher{c: c, expected: strUniterState}).Return(err) 296 } 297 298 func (s *StateOpsSuite) expectState(c *gc.C, st operation.State) { 299 data, err := yaml.Marshal(st) 300 c.Assert(err, jc.ErrorIsNil) 301 stStr := string(data) 302 303 mExp := s.mockStateRW.EXPECT() 304 mExp.State().Return(params.UnitStateResult{UniterState: stStr}, nil) 305 } 306 307 type unitStateMatcher struct { 308 c *gc.C 309 expected string 310 } 311 312 func (m unitStateMatcher) Matches(x interface{}) bool { 313 obtained, ok := x.(params.SetUnitStateArg) 314 if !ok { 315 return false 316 } 317 318 if obtained.UniterState == nil || m.expected != *obtained.UniterState { 319 m.c.Fatalf("unitStateMatcher: expected (%s) obtained (%s)", m.expected, *obtained.UniterState) 320 return false 321 } 322 323 return true 324 } 325 326 func (m unitStateMatcher) String() string { 327 return "Match the contents of the UniterState pointer in params.SetUnitStateArg" 328 }