forked from tsuru/tsuru
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprovision.go
731 lines (610 loc) · 19.9 KB
/
provision.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package provision provides interfaces that need to be satisfied in order to
// implement a new provisioner on tsuru.
package provision
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/url"
"time"
"github.com/fsouza/go-dockerclient"
"github.com/pkg/errors"
"github.com/tsuru/tsuru/app/bind"
"github.com/tsuru/tsuru/event"
"github.com/tsuru/tsuru/router"
appTypes "github.com/tsuru/tsuru/types/app"
)
const (
defaultDockerProvisioner = "docker"
DefaultHealthcheckScheme = "http"
PoolMetadataName = "pool"
IaaSIDMetadataName = "iaas-id"
IaaSMetadataName = "iaas"
)
var (
ErrInvalidStatus = errors.New("invalid status")
ErrEmptyApp = errors.New("no units for this app")
ErrNodeNotFound = errors.New("node not found")
DefaultProvisioner = defaultDockerProvisioner
)
type UnitNotFoundError struct {
ID string
}
func (e *UnitNotFoundError) Error() string {
return fmt.Sprintf("unit %q not found", e.ID)
}
type InvalidProcessError struct {
Msg string
}
func (e InvalidProcessError) Error() string {
return fmt.Sprintf("process error: %s", e.Msg)
}
type ProvisionerNotSupported struct {
Prov Provisioner
Action string
}
func (e ProvisionerNotSupported) Error() string {
return fmt.Sprintf("provisioner %q does not support %s", e.Prov.GetName(), e.Action)
}
// Status represents the status of a unit in tsuru.
type Status string
func (s Status) String() string {
return string(s)
}
func ParseStatus(status string) (Status, error) {
switch status {
case "created":
return StatusCreated, nil
case "building":
return StatusBuilding, nil
case "error":
return StatusError, nil
case "started":
return StatusStarted, nil
case "starting":
return StatusStarting, nil
case "stopped":
return StatusStopped, nil
case "asleep":
return StatusAsleep, nil
}
return Status(""), ErrInvalidStatus
}
// Flow:
// +----------------------------------------------+
// | |
// | Start |
// +----------+ | +---------+ |
// | Building | +---------------------+| Stopped | |
// +----------+ | +---------+ |
// ^ | ^ |
// | | | |
// deploy unit | Stop |
// | | | |
// + v RegisterUnit + +
// +---------+ app unit +----------+ SetUnitStatus +---------+ Sleep +--------+
// | Created | +---------> | Starting | +-------------> | Started |+------->| Asleep |
// +---------+ +----------+ +---------+ +--------+
// + ^ +
// | | |
// SetUnitStatus | |
// | | |
// v | |
// +-------+ SetUnitStatus | |
// | Error | +-------------------+ |
// +-------+ <---------------------+
const (
// StatusCreated is the initial status of a unit in the database,
// it should transition shortly to a more specific status
StatusCreated = Status("created")
// StatusBuilding is the status for units being provisioned by the
// provisioner, like in the deployment.
StatusBuilding = Status("building")
// StatusError is the status for units that failed to start, because of
// an application error.
StatusError = Status("error")
// StatusStarting is set when the container is started in docker.
StatusStarting = Status("starting")
// StatusStarted is for cases where the unit is up and running, and bound
// to the proper status, it's set by RegisterUnit and SetUnitStatus.
StatusStarted = Status("started")
// StatusStopped is for cases where the unit has been stopped.
StatusStopped = Status("stopped")
// StatusAsleep is for cases where the unit has been asleep.
StatusAsleep = Status("asleep")
)
// Unit represents a provision unit. Can be a machine, container or anything
// IP-addressable.
type Unit struct {
ID string
Name string
AppName string
ProcessName string
Type string
IP string
Status Status
Address *url.URL
}
// GetName returns the name of the unit.
func (u *Unit) GetID() string {
return u.ID
}
// GetIp returns the Unit.IP.
func (u *Unit) GetIp() string {
return u.IP
}
func (u *Unit) MarshalJSON() ([]byte, error) {
type UnitForMarshal Unit
host, port, _ := net.SplitHostPort(u.Address.Host)
// New fields added for compatibility with old routes returning containers.
return json.Marshal(&struct {
*UnitForMarshal
HostAddr string
HostPort string
IP string
}{
UnitForMarshal: (*UnitForMarshal)(u),
HostAddr: host,
HostPort: port,
IP: u.IP,
})
}
// Available returns true if the unit is available. It will return true
// whenever the unit itself is available, even when the application process is
// not.
func (u *Unit) Available() bool {
return u.Status == StatusStarted ||
u.Status == StatusStarting ||
u.Status == StatusError
}
// Named is something that has a name, providing the GetName method.
type Named interface {
GetName() string
}
// RunArgs groups together the arguments to run an App.
type RunArgs struct {
Once bool
Isolated bool
}
// App represents a tsuru app.
//
// It contains only relevant information for provisioning.
type App interface {
Named
BindUnit(*Unit) error
UnbindUnit(*Unit) error
// GetPlatform returns the platform (type) of the app. It is equivalent
// to the Unit `Type` field.
GetPlatform() string
// GetDeploy returns the deploys that an app has.
GetDeploys() uint
Envs() map[string]bind.EnvVar
GetMemory() int64
GetSwap() int64
GetCpuShare() int
GetUpdatePlatform() bool
GetRouters() []appTypes.AppRouter
GetPool() string
GetTeamOwner() string
SetQuotaInUse(int) error
}
type AppLock interface {
json.Marshaler
GetLocked() bool
GetReason() string
GetOwner() string
GetAcquireDate() time.Time
}
// RollbackableDeployer is a provisioner that allows rolling back to a
// previously deployed version.
type RollbackableDeployer interface {
Rollback(App, string, *event.Event) (string, error)
}
type BuilderDockerClient interface {
PullAndCreateContainer(opts docker.CreateContainerOptions, w io.Writer) (*docker.Container, string, error)
RemoveContainer(opts docker.RemoveContainerOptions) error
StartContainer(id string, hostConfig *docker.HostConfig) error
StopContainer(id string, timeout uint) error
InspectContainer(id string) (*docker.Container, error)
CommitContainer(docker.CommitContainerOptions) (*docker.Image, error)
DownloadFromContainer(string, docker.DownloadFromContainerOptions) error
UploadToContainer(string, docker.UploadToContainerOptions) error
AttachToContainerNonBlocking(opts docker.AttachToContainerOptions) (docker.CloseWaiter, error)
AttachToContainer(opts docker.AttachToContainerOptions) error
WaitContainer(id string) (int, error)
BuildImage(opts docker.BuildImageOptions) error
PushImage(docker.PushImageOptions, docker.AuthConfiguration) error
InspectImage(string) (*docker.Image, error)
TagImage(string, docker.TagImageOptions) error
RemoveImage(name string) error
ImageHistory(name string) ([]docker.ImageHistory, error)
SetTimeout(timeout time.Duration)
}
type ExecDockerClient interface {
CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error)
StartExec(execId string, opts docker.StartExecOptions) error
ResizeExecTTY(execId string, height, width int) error
InspectExec(execId string) (*docker.ExecInspect, error)
}
type BuilderKubeClient interface {
BuildPod(App, *event.Event, io.Reader, string) (string, error)
BuildImage(name, image string, inputStream io.Reader, output io.Writer, ctx context.Context) error
ImageTagPushAndInspect(App, string, string) (*docker.Image, string, *TsuruYamlData, error)
}
// BuilderDeploy is a provisioner that allows deploy builded image.
type BuilderDeploy interface {
Deploy(App, string, *event.Event) (string, error)
}
type BuilderDeployDockerClient interface {
BuilderDeploy
GetClient(App) (BuilderDockerClient, error)
}
type BuilderDeployKubeClient interface {
BuilderDeploy
GetClient(App) (BuilderKubeClient, error)
}
// Provisioner is the basic interface of this package.
//
// Any tsuru provisioner must implement this interface in order to provision
// tsuru apps.
type Provisioner interface {
Named
// Provision is called when tsuru is creating the app.
Provision(App) error
// Destroy is called when tsuru is destroying the app.
Destroy(App) error
// AddUnits adds units to an app. The first parameter is the app, the
// second is the number of units to be added.
//
// It returns a slice containing all added units
AddUnits(App, uint, string, io.Writer) error
// RemoveUnits "undoes" AddUnits, removing the given number of units
// from the app.
RemoveUnits(App, uint, string, io.Writer) error
// Restart restarts the units of the application, with an optional
// string parameter represeting the name of the process to start. When
// the process is empty, Restart will restart all units of the
// application.
Restart(App, string, io.Writer) error
// Start starts the units of the application, with an optional string
// parameter representing the name of the process to start. When the
// process is empty, Start will start all units of the application.
Start(App, string) error
// Stop stops the units of the application, with an optional string
// parameter representing the name of the process to start. When the
// process is empty, Stop will stop all units of the application.
Stop(App, string) error
// Units returns information about units by App.
Units(...App) ([]Unit, error)
// RoutableAddresses returns the addresses used to access an application.
RoutableAddresses(App) ([]url.URL, error)
// Register a unit after the container has been created or restarted.
RegisterUnit(App, string, map[string]interface{}) error
}
type ExecOptions struct {
App App
Stdout io.Writer
Stderr io.Writer
Stdin io.Reader
Width int
Height int
Term string
Cmds []string
Units []string
}
type ExecutableProvisioner interface {
ExecuteCommand(opts ExecOptions) error
}
// SleepableProvisioner is a provisioner that allows putting applications to
// sleep.
type SleepableProvisioner interface {
// Sleep puts the units of the application to sleep, with an optional string
// parameter representing the name of the process to sleep. When the
// process is empty, Sleep will put all units of the application to sleep.
Sleep(App, string) error
}
// UpdatableProvisioner is a provisioner that stores data about applications
// and must be notified when they are updated
type UpdatableProvisioner interface {
UpdateApp(old, new App, w io.Writer) error
}
// MessageProvisioner is a provisioner that provides a welcome message for
// logging.
type MessageProvisioner interface {
StartupMessage() (string, error)
}
// InitializableProvisioner is a provisioner that provides an initialization
// method that should be called when the app is started
type InitializableProvisioner interface {
Initialize() error
}
// OptionalLogsProvisioner is a provisioner that allows optionally disabling
// logs for a given app.
type OptionalLogsProvisioner interface {
// Checks if logs are enabled for given app.
LogsEnabled(App) (bool, string, error)
}
// UnitStatusProvisioner is a provisioner that receive notifications about unit
// status changes.
type UnitStatusProvisioner interface {
// SetUnitStatus changes the status of a unit.
SetUnitStatus(Unit, Status) error
}
type AddNodeOptions struct {
IaaSID string
Address string
Pool string
Metadata map[string]string
Register bool
CaCert []byte
ClientCert []byte
ClientKey []byte
WaitTO time.Duration
}
type RemoveNodeOptions struct {
Address string
Rebalance bool
Writer io.Writer
}
type UpdateNodeOptions struct {
Address string
Pool string
Metadata map[string]string
Enable bool
Disable bool
}
type NodeProvisioner interface {
Named
// ListNodes returns a list of all nodes registered in the provisioner.
ListNodes(addressFilter []string) ([]Node, error)
// GetNode retrieves an existing node by its address.
GetNode(address string) (Node, error)
// AddNode adds a new node in the provisioner.
AddNode(AddNodeOptions) error
// RemoveNode removes an existing node.
RemoveNode(RemoveNodeOptions) error
// UpdateNode can be used to enable/disable a node and update its metadata.
UpdateNode(UpdateNodeOptions) error
// NodeForNodeData finds a node matching the received NodeStatusData.
NodeForNodeData(NodeStatusData) (Node, error)
}
type RebalanceNodesOptions struct {
Event *event.Event
Pool string
MetadataFilter map[string]string
AppFilter []string
Dry bool
Force bool
}
type NodeRebalanceProvisioner interface {
RebalanceNodes(RebalanceNodesOptions) (bool, error)
}
type NodeContainerProvisioner interface {
UpgradeNodeContainer(name string, pool string, writer io.Writer) error
RemoveNodeContainer(name string, pool string, writer io.Writer) error
}
// UnitFinderProvisioner is a provisioner that allows finding a specific unit
// by its id. New provisioners should not implement this interface, this was
// only used during events format migration and is exclusive to docker
// provisioner.
type UnitFinderProvisioner interface {
// GetAppFromUnitID returns an app from unit id
GetAppFromUnitID(string) (App, error)
}
// AppFilterProvisioner is a provisioner that allows filtering apps by the
// state of its units.
type AppFilterProvisioner interface {
FilterAppsByUnitStatus([]App, []string) ([]App, error)
}
type VolumeProvisioner interface {
IsVolumeProvisioned(volumeName, pool string) (bool, error)
DeleteVolume(volumeName, pool string) error
}
type CleanImageProvisioner interface {
CleanImage(appName string, image string) error
}
type Node interface {
Pool() string
IaaSID() string
Address() string
Status() string
// Metadata returns node metadata exclusively managed by tsuru
Metadata() map[string]string
Units() ([]Unit, error)
Provisioner() NodeProvisioner
// MetadataNoPrefix returns node metadata managed by tsuru without any
// tsuru specific prefix. This can be used with iaas providers.
MetadataNoPrefix() map[string]string
}
type NodeExtraData interface {
// ExtraData returns node metadata not managed by tsuru, like metadata
// added by external sources.
ExtraData() map[string]string
}
type NodeHealthChecker interface {
Node
FailureCount() int
HasSuccess() bool
ResetFailures()
}
type NodeSpec struct {
// BSON tag for bson serialized compatibility with cluster.Node
Address string `bson:"_id"`
IaaSID string
Metadata map[string]string
Status string
Pool string
Provisioner string
}
func NodeToSpec(n Node) NodeSpec {
metadata := map[string]string{}
if extra, ok := n.(NodeExtraData); ok {
for k, v := range extra.ExtraData() {
metadata[k] = v
}
}
for k, v := range n.Metadata() {
metadata[k] = v
}
var provName string
prov := n.Provisioner()
if prov != nil {
provName = prov.GetName()
}
return NodeSpec{
Address: n.Address(),
IaaSID: n.IaaSID(),
Metadata: metadata,
Status: n.Status(),
Pool: n.Pool(),
Provisioner: provName,
}
}
func NodeToJSON(n Node) ([]byte, error) {
return json.Marshal(NodeToSpec(n))
}
type NodeStatusData struct {
Addrs []string
Units []UnitStatusData
Checks []NodeCheckResult
}
type UnitStatusData struct {
ID string
Name string
Status Status
}
type NodeCheckResult struct {
Name string
Err string
Successful bool
}
type provisionerFactory func() (Provisioner, error)
var provisioners = make(map[string]provisionerFactory)
// Register registers a new provisioner in the Provisioner registry.
func Register(name string, pFunc provisionerFactory) {
provisioners[name] = pFunc
}
// Unregister unregisters a provisioner.
func Unregister(name string) {
delete(provisioners, name)
}
// Get gets the named provisioner from the registry.
func Get(name string) (Provisioner, error) {
pFunc, ok := provisioners[name]
if !ok {
return nil, errors.Errorf("unknown provisioner: %q", name)
}
return pFunc()
}
func GetDefault() (Provisioner, error) {
if DefaultProvisioner == "" {
DefaultProvisioner = defaultDockerProvisioner
}
return Get(DefaultProvisioner)
}
// Registry returns the list of registered provisioners.
func Registry() ([]Provisioner, error) {
registry := make([]Provisioner, 0, len(provisioners))
for _, pFunc := range provisioners {
p, err := pFunc()
if err != nil {
return nil, err
}
registry = append(registry, p)
}
return registry, nil
}
func InitializeAll() error {
provisioners, err := Registry()
if err != nil {
return err
}
var startupMessage string
for _, p := range provisioners {
if initializableProvisioner, ok := p.(InitializableProvisioner); ok {
err = initializableProvisioner.Initialize()
if err != nil {
return err
}
}
if messageProvisioner, ok := p.(MessageProvisioner); ok {
startupMessage, err = messageProvisioner.StartupMessage()
if err == nil && startupMessage != "" {
fmt.Print(startupMessage)
}
}
}
return nil
}
// Error represents a provisioning error. It encapsulates further errors.
type Error struct {
Reason string
Err error
}
// Error is the string representation of a provisioning error.
func (e *Error) Error() string {
var err string
if e.Err != nil {
err = e.Err.Error() + ": " + e.Reason
} else {
err = e.Reason
}
return err
}
type TsuruYamlData struct {
Hooks TsuruYamlHooks `bson:",omitempty"`
Healthcheck TsuruYamlHealthcheck `bson:",omitempty"`
}
type TsuruYamlHooks struct {
Restart TsuruYamlRestartHooks `bson:",omitempty"`
Build []string `bson:",omitempty"`
}
type TsuruYamlRestartHooks struct {
Before []string `bson:",omitempty"`
After []string `bson:",omitempty"`
}
type TsuruYamlHealthcheck struct {
Path string
Method string
Status int
Scheme string
Match string `bson:",omitempty"`
RouterBody string `json:"router_body" yaml:"router_body" bson:"router_body,omitempty"`
UseInRouter bool `json:"use_in_router" yaml:"use_in_router" bson:"use_in_router,omitempty"`
ForceRestart bool `json:"force_restart" yaml:"force_restart" bson:"force_restart,omitempty"`
AllowedFailures int `json:"allowed_failures" yaml:"allowed_failures" bson:"allowed_failures,omitempty"`
IntervalSeconds int `json:"interval_seconds" yaml:"interval_seconds" bson:"interval_seconds,omitempty"`
TimeoutSeconds int `json:"timeout_seconds" yaml:"timeout_seconds" bson:"timeout_seconds,omitempty"`
}
func (hc TsuruYamlHealthcheck) ToRouterHC() router.HealthcheckData {
if hc.UseInRouter {
return router.HealthcheckData{
Path: hc.Path,
Status: hc.Status,
Body: hc.RouterBody,
}
}
return router.HealthcheckData{
Path: "/",
}
}
type ErrUnitStartup struct {
Err error
}
func (e ErrUnitStartup) Error() string {
return e.Err.Error()
}
func (e ErrUnitStartup) IsStartupError() bool {
return true
}
func IsStartupError(err error) bool {
se, ok := errors.Cause(err).(interface {
IsStartupError() bool
})
return ok && se.IsStartupError()
}