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

introduce Newable service #417

Merged
merged 23 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions api/delete_deleter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package api

import "github.com/mesg-foundation/core/database/services"
import (
"github.com/mesg-foundation/core/database/services"
"github.com/mesg-foundation/core/service"
)

// serviceDeleter provides functionalities to delete a MESG service.
type serviceDeleter struct {
Expand All @@ -16,11 +19,15 @@ func newServiceDeleter(api *API) *serviceDeleter {

// Delete stops and deletes service serviceID.
func (d *serviceDeleter) Delete(serviceID string) error {
service, err := services.Get(serviceID)
s, err := services.Get(serviceID)
if err != nil {
return err
}
if err := service.Stop(); err != nil {
s, err = service.FromService(s, service.ContainerOption(d.api.container))
if err != nil {
return err
}
if err := s.Stop(); err != nil {
return err
}
return services.Delete(serviceID)
Expand Down
2 changes: 1 addition & 1 deletion api/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type DeployServiceOption func(*serviceDeployer)
// DeployServiceStatusOption receives chan statuses to send deploy statuses.
func DeployServiceStatusOption(statuses chan DeployStatus) DeployServiceOption {
return func(deployer *serviceDeployer) {
deployer.Statuses = statuses
deployer.statuses = statuses
}
}

Expand Down
107 changes: 34 additions & 73 deletions api/deploy_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"

"github.com/docker/docker/pkg/archive"
"github.com/logrusorgru/aurora"
"github.com/mesg-foundation/core/database/services"
"github.com/mesg-foundation/core/service"
"github.com/mesg-foundation/core/service/importer"
"github.com/mesg-foundation/core/x/xdocker/xarchive"
"github.com/mesg-foundation/core/x/xgit"
uuid "github.com/satori/go.uuid"
)

// serviceDeployer provides functionalities to deploy a MESG service.
type serviceDeployer struct {
// Statuses receives status messages produced during deployment.
Statuses chan DeployStatus
// statuses receives status messages produced during deployment.
statuses chan DeployStatus

api *API
}
Expand Down Expand Up @@ -59,108 +58,70 @@ func (d *serviceDeployer) FromGitURL(url string) (*service.Service, *importer.Va
if err != nil {
return nil, nil, err
}
defer os.RemoveAll(path)
if err := xgit.Clone(url, path); err != nil {
return nil, nil, err
}
d.sendStatus(fmt.Sprintf("%s Service downloaded with success.", aurora.Green("✔")), DONE)
return d.deploy(path)
r, err := xarchive.GzippedTar(path)
if err != nil {
return nil, nil, err
}
return d.deploy(r)
}

// FromGzippedTar deploys a service from a gzipped tarball.
func (d *serviceDeployer) FromGzippedTar(r io.Reader) (*service.Service, *importer.ValidationError, error) {
d.sendStatus("Sending service context to core daemon...", RUNNING)
path, err := d.createTempDir()
if err != nil {
return nil, nil, err
}
if err := archive.Untar(r, path, &archive.TarOptions{
Compression: archive.Gzip,
}); err != nil {
return nil, nil, err
}
d.sendStatus(fmt.Sprintf("%s Service context sent to core daemon with success.", aurora.Green("✔")), DONE)
return d.deploy(path)
return d.deploy(r)
}

// deploy deploys a service in path.
func (d *serviceDeployer) deploy(path string) (*service.Service, *importer.ValidationError, error) {
defer os.RemoveAll(path)

s, err := importer.From(path)
func (d *serviceDeployer) deploy(r io.Reader) (*service.Service, *importer.ValidationError, error) {
statuses := make(chan service.DeployStatus, 0)
go d.forwardDeployStatuses(statuses)

s, err := service.New(r,
service.ContainerOption(d.api.container),
service.DeployStatusOption(statuses),
)
validationErr, err := d.assertValidationError(err)
if err != nil {
return nil, nil, err
}
if validationErr != nil {
return nil, validationErr, nil
}

d.sendStatus("Building Docker image...", RUNNING)
imageHash, err := d.api.container.Build(path)
if err != nil {
return nil, nil, err
}
if _, err := os.Stat(filepath.Join(path, ".mesgignore")); err == nil {
// TODO: remove for a future release
d.sendStatus(fmt.Sprintf("%s [DEPRECATED] Please use .dockerignore instead of .mesgignore", aurora.Red("⨯")), DONE)
}
d.sendStatus(fmt.Sprintf("%s Image built with success.", aurora.Green("✔")), DONE)
d.adjustFields(s)
d.injectConfigurationInDependencies(s, imageHash)
s.ID = s.Hash()

d.sendStatus(fmt.Sprintf("%s Completed.", aurora.Green("✔")), DONE)
return s, nil, services.Save(s)
}

func (d *serviceDeployer) adjustFields(s *service.Service) {
for eventKey, event := range s.Events {
event.Key = eventKey
event.ServiceName = s.Name
}
for taskKey, task := range s.Tasks {
task.Key = taskKey
task.ServiceName = s.Name
for outputKey, output := range task.Outputs {
output.Key = outputKey
output.TaskKey = taskKey
output.ServiceName = s.Name
}
}
}

func (d *serviceDeployer) injectConfigurationInDependencies(s *service.Service, imageHash string) {
config := s.Configuration
if config == nil {
config = &service.Dependency{}
}
dependency := &service.Dependency{
Command: config.Command,
Ports: config.Ports,
Volumes: config.Volumes,
VolumesFrom: config.VolumesFrom,
Image: imageHash,
}
if s.Dependencies == nil {
s.Dependencies = make(map[string]*service.Dependency)
}
s.Dependencies["service"] = dependency
}

func (d *serviceDeployer) createTempDir() (path string, err error) {
return ioutil.TempDir("", "mesg-"+uuid.NewV4().String())
}

// sendStatus sends a status message.
func (d *serviceDeployer) sendStatus(message string, typ StatusType) {
if d.Statuses != nil {
d.Statuses <- DeployStatus{
if d.statuses != nil {
d.statuses <- DeployStatus{
Message: message,
Type: typ,
}
}
}

// forwardStatuses forwards status messages.
func (d *serviceDeployer) forwardDeployStatuses(statuses chan service.DeployStatus) {
for status := range statuses {
var t StatusType
switch status.Type {
case service.DRUNNING:
t = RUNNING
case service.DDONE:
t = DONE
}
d.sendStatus(status.Message, t)
}
}

func (d *serviceDeployer) assertValidationError(err error) (*importer.ValidationError, error) {
if err == nil {
return nil, nil
Expand Down
56 changes: 0 additions & 56 deletions api/deploy_deployer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"os"
"testing"

"github.com/mesg-foundation/core/service"
"github.com/stretchr/testify/require"
)

Expand All @@ -26,58 +25,3 @@ func TestRemoveTempFolder(t *testing.T) {
err := os.RemoveAll(path)
require.Nil(t, err)
}

func TestInjectConfigurationInDependencies(t *testing.T) {
a, _ := newAPIAndDockerTest(t)
deployer := newServiceDeployer(a)

s := &service.Service{}
deployer.injectConfigurationInDependencies(s, "TestInjectConfigurationInDependencies")
require.Equal(t, s.Dependencies["service"].Image, "TestInjectConfigurationInDependencies")
}

func TestInjectConfigurationInDependenciesWithConfig(t *testing.T) {
a, _ := newAPIAndDockerTest(t)
deployer := newServiceDeployer(a)

s := &service.Service{
Configuration: &service.Dependency{
Command: "xxx",
Image: "yyy",
},
}
deployer.injectConfigurationInDependencies(s, "TestInjectConfigurationInDependenciesWithConfig")
require.Equal(t, s.Dependencies["service"].Image, "TestInjectConfigurationInDependenciesWithConfig")
require.Equal(t, s.Dependencies["service"].Command, "xxx")
}

func TestInjectConfigurationInDependenciesWithDependency(t *testing.T) {
a, _ := newAPIAndDockerTest(t)
deployer := newServiceDeployer(a)

s := &service.Service{
Dependencies: map[string]*service.Dependency{
"test": {
Image: "xxx",
},
},
}
deployer.injectConfigurationInDependencies(s, "TestInjectConfigurationInDependenciesWithDependency")
require.Equal(t, s.Dependencies["service"].Image, "TestInjectConfigurationInDependenciesWithDependency")
require.Equal(t, s.Dependencies["test"].Image, "xxx")
}

func TestInjectConfigurationInDependenciesWithDependencyOverride(t *testing.T) {
a, _ := newAPIAndDockerTest(t)
deployer := newServiceDeployer(a)

s := &service.Service{
Dependencies: map[string]*service.Dependency{
"service": {
Image: "xxx",
},
},
}
deployer.injectConfigurationInDependencies(s, "TestInjectConfigurationInDependenciesWithDependencyOverride")
require.Equal(t, s.Dependencies["service"].Image, "TestInjectConfigurationInDependenciesWithDependencyOverride")
}
39 changes: 27 additions & 12 deletions api/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"sync"
"testing"

"github.com/cnf/structhash"
"github.com/logrusorgru/aurora"
"github.com/mesg-foundation/core/service/importer"
"github.com/mesg-foundation/core/x/xdocker/xarchive"
Expand All @@ -34,27 +33,27 @@ func TestDeployService(t *testing.T) {
service, validationError, err := a.DeployService(archive, DeployServiceStatusOption(statuses))
require.Nil(t, validationError)
require.Nil(t, err)
require.Equal(t, 1, structhash.Version(service.ID))
require.Len(t, service.ID, 40)
}()

require.Equal(t, DeployStatus{
Message: "Sending service context to core daemon...",
Message: "Receiving service context...",
Type: RUNNING,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: fmt.Sprintf("%s Service context sent to core daemon with success.", aurora.Green("✔")),
Message: fmt.Sprintf("%s Service context received with success.", aurora.Green("✔")),
Type: DONE,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: "Building Docker image...",
Type: RUNNING,
Message: fmt.Sprintf("%s [DEPRECATED] Please use .dockerignore instead of .mesgignore", aurora.Red("⨯")),
Type: DONE,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: fmt.Sprintf("%s [DEPRECATED] Please use .dockerignore instead of .mesgignore", aurora.Red("⨯")),
Type: DONE,
Message: "Building Docker image...",
Type: RUNNING,
}, <-statuses)

require.Equal(t, DeployStatus{
Expand Down Expand Up @@ -92,9 +91,15 @@ func TestDeployInvalidService(t *testing.T) {
require.Equal(t, (&importer.ValidationError{}).Error(), validationError.Error())
}()

require.Equal(t, "Sending service context to core daemon...", (<-statuses).Message)
require.Equal(t, fmt.Sprintf("%s Service context sent to core daemon with success.", aurora.Green("✔")),
(<-statuses).Message)
require.Equal(t, DeployStatus{
Message: "Receiving service context...",
Type: RUNNING,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: fmt.Sprintf("%s Service context received with success.", aurora.Green("✔")),
Type: DONE,
}, <-statuses)

select {
case <-statuses:
Expand All @@ -120,7 +125,7 @@ func TestDeployServiceFromURL(t *testing.T) {
service, validationError, err := a.DeployServiceFromURL(url, DeployServiceStatusOption(statuses))
require.Nil(t, validationError)
require.Nil(t, err)
require.Equal(t, 1, structhash.Version(service.ID))
require.Len(t, service.ID, 40)
}()

require.Equal(t, DeployStatus{
Expand All @@ -133,6 +138,16 @@ func TestDeployServiceFromURL(t *testing.T) {
Type: DONE,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: "Receiving service context...",
Type: RUNNING,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: fmt.Sprintf("%s Service context received with success.", aurora.Green("✔")),
Type: DONE,
}, <-statuses)

require.Equal(t, DeployStatus{
Message: "Building Docker image...",
Type: RUNNING,
Expand Down
2 changes: 1 addition & 1 deletion api/emit_event_emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (e *eventEmitter) Emit(token, eventKey string, eventData map[string]interfa
if err != nil {
return err
}
event, err := event.Create(&s, eventKey, eventData)
event, err := event.Create(s, eventKey, eventData)
if err != nil {
return err
}
Expand Down
10 changes: 7 additions & 3 deletions api/execute_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ func (e *taskExecutor) Execute(serviceID, taskKey string, inputData map[string]i
if err != nil {
return "", err
}
if err := e.checkServiceStatus(&s); err != nil {
s, err = service.FromService(s, service.ContainerOption(e.api.container))
if err != nil {
return "", err
}
if err := e.checkServiceStatus(s); err != nil {
return "", err
}
return e.execute(&s, taskKey, inputData, tags)
return e.execute(s, taskKey, inputData, tags)
}

// checkServiceStatus checks service status. A task should be executed only if
Expand All @@ -41,7 +45,7 @@ func (e *taskExecutor) checkServiceStatus(s *service.Service) error {
return err
}
if status != service.RUNNING {
return &NotRunningServiceError{ServiceID: s.Hash()}
return &NotRunningServiceError{ServiceID: s.ID}
}
return nil
}
Expand Down
Loading