[ADD] add group app backup feature (#72)

This commit is contained in:
barnett 2018-05-23 11:08:44 +08:00
parent 97c5527458
commit 8cd2dc9697
27 changed files with 1145 additions and 77 deletions

View File

@ -137,6 +137,4 @@ type AppInterface interface {
Download(w http.ResponseWriter, r *http.Request)
Upload(w http.ResponseWriter, r *http.Request)
ImportApp(w http.ResponseWriter, r *http.Request)
BackupApp(w http.ResponseWriter, r *http.Request)
RecoverApp(w http.ResponseWriter, r *http.Request)
}
}

View File

@ -87,6 +87,11 @@ func (v2 *V2) tenantNameRouter() chi.Router {
r.Get("/chargesverify", controller.ChargesVerifyController)
//get some service pod info
r.Get("/pods", controller.Pods)
//app backup
r.Get("/groupapp/backups", controller.Backups)
r.Get("/groupapp/backups/{backup_id}", controller.GetBackup)
r.Get("/groupapp/restore", controller.Restore)
r.Post("/groupapp/backups", controller.NewBackups)
return r
}
@ -223,9 +228,6 @@ func (v2 *V2) appRouter() chi.Router {
r.Post("/import", controller.GetManager().ImportApp)
r.Get("/import/{eventId}", controller.GetManager().ExportApp)
r.Post("/backup", controller.GetManager().BackupApp)
r.Post("/recover", controller.GetManager().RecoverApp)
return r
}

View File

@ -167,11 +167,3 @@ func (a *AppStruct) ImportApp(w http.ResponseWriter, r *http.Request) {
}
}
func (a *AppStruct) BackupApp(w http.ResponseWriter, r *http.Request) {
//TODO
}
func (a *AppStruct) RecoverApp(w http.ResponseWriter, r *http.Request) {
//TODO
}

75
api/controller/group.go Normal file
View File

@ -0,0 +1,75 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"github.com/go-chi/chi"
"github.com/goodrain/rainbond/api/handler"
"github.com/goodrain/rainbond/api/handler/group"
httputil "github.com/goodrain/rainbond/util/http"
)
//Backups list all backup history by group app
func Backups(w http.ResponseWriter, r *http.Request) {
groupID := r.FormValue("group_id")
if groupID == "" {
httputil.ReturnError(r, w, 400, "group id can not be empty")
return
}
list, err := handler.GetAPPBackupHandler().GetBackupByGroupID(groupID)
if err != nil {
err.Handle(r, w)
return
}
httputil.ReturnSuccess(r, w, list)
}
//NewBackups new group app backup
func NewBackups(w http.ResponseWriter, r *http.Request) {
var gb group.Backup
ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &gb.Body, nil)
if !ok {
return
}
bean, err := handler.GetAPPBackupHandler().NewBackup(gb)
if err != nil {
err.Handle(r, w)
return
}
httputil.ReturnSuccess(r, w, bean)
}
//Restore restore group app
func Restore(w http.ResponseWriter, r *http.Request) {
}
//GetBackup get one backup status
func GetBackup(w http.ResponseWriter, r *http.Request) {
backupID := chi.URLParam(r, "backup_id")
bean, err := handler.GetAPPBackupHandler().GetBackup(backupID)
if err != nil {
err.Handle(r, w)
return
}
httputil.ReturnSuccess(r, w, bean)
}

View File

@ -3,19 +3,20 @@ package handler
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/goodrain/rainbond/api/db"
"github.com/goodrain/rainbond/api/model"
"github.com/goodrain/rainbond/api/util"
"github.com/goodrain/rainbond/mq/api/grpc/pb"
dbmodel "github.com/goodrain/rainbond/db/model"
"io/ioutil"
"os"
"fmt"
"github.com/tidwall/gjson"
"github.com/goodrain/rainbond/mq/api/grpc/pb"
"github.com/pkg/errors"
"strings"
"strconv"
"github.com/tidwall/gjson"
)
type AppAction struct {
@ -56,6 +57,7 @@ func (a *AppAction) Complete(tr *model.ExportAppStruct) error {
return nil
}
//ExportApp ExportApp
func (a *AppAction) ExportApp(tr *model.ExportAppStruct) error {
// 保存元数据到组目录
if err := saveMetadata(tr); err != nil {
@ -88,7 +90,6 @@ func (a *AppAction) ExportApp(tr *model.ExportAppStruct) error {
logrus.Error("Failed to Enqueue MQ for ExportApp:", err)
return err
}
logrus.Debugf("equeue mq build plugin from image success")
return nil
}
@ -120,7 +121,6 @@ func (a *AppAction) ImportApp(app *dbmodel.AppStatus) error {
return nil
}
func saveMetadata(tr *model.ExportAppStruct) error {
// 创建应用组目录
os.MkdirAll(tr.SourceDir, 0755)
@ -164,4 +164,4 @@ func unicode2zh(uText string) (context string) {
context = strings.TrimSpace(context)
return context
}
}

View File

@ -0,0 +1,276 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package group
import (
"context"
"fmt"
"io/ioutil"
"os"
"github.com/goodrain/rainbond/event"
"github.com/Sirupsen/logrus"
apidb "github.com/goodrain/rainbond/api/db"
"github.com/goodrain/rainbond/appruntimesync/client"
"github.com/goodrain/rainbond/mq/api/grpc/pb"
"github.com/jinzhu/gorm"
"github.com/pquerna/ffjson/ffjson"
"github.com/goodrain/rainbond/api/util"
"github.com/goodrain/rainbond/db"
dbmodel "github.com/goodrain/rainbond/db/model"
core_util "github.com/goodrain/rainbond/util"
)
//Backup GroupBackup
// swagger:parameters groupBackup
type Backup struct {
// in: path
// required: true
TenantName string `json:"tenant_name"`
Body struct {
EventID string `json:"event_id" validate:"event_id|required"`
GroupID string `json:"group_id" validate:"group_name|required"`
Metadata string `json:"metadata,omitempty" validate:"metadata|required"`
ServiceIDs []string `json:"service_ids" validate:"service_ids|required"`
Mode string `json:"mode" validate:"mode|required|in:full-online,full-offline"`
Version string `json:"version" validate:"version|required"`
SlugInfo struct {
FTPNamespace string `json:"ftp_namespace"`
FTPHost string `json:"ftp_host"`
FTPPort string `json:"ftp_port"`
FTPUser string `json:"ftp_username"`
FTPPassword string `json:"ftp_password"`
} `json:"slug_info,omitempty"`
ImageInfo struct {
HubURL string `json:"hub_url"`
HubUser string `json:"hub_user"`
HubPassword string `json:"hub_password"`
Namespace string `json:"namespace"`
IsTrust bool `json:"is_trust,omitempty"`
} `json:"image_info,omitempty"`
SourceDir string `json:"source_dir"`
BackupID string `json:"backup_id,omitempty"`
}
}
//BackupHandle group app backup handle
type BackupHandle struct {
MQClient pb.TaskQueueClient
statusCli *client.AppRuntimeSyncClient
}
//CreateBackupHandle CreateBackupHandle
func CreateBackupHandle(MQClient pb.TaskQueueClient, statusCli *client.AppRuntimeSyncClient) *BackupHandle {
return &BackupHandle{MQClient: MQClient, statusCli: statusCli}
}
//NewBackup new backup task
func (h *BackupHandle) NewBackup(b Backup) (*dbmodel.AppBackup, *util.APIHandleError) {
logger := event.GetManager().GetLogger(b.Body.EventID)
var appBackup = dbmodel.AppBackup{
EventID: b.Body.EventID,
BackupID: core_util.NewUUID(),
GroupID: b.Body.GroupID,
Status: "staring",
Version: b.Body.Version,
BackupMode: b.Body.Mode,
}
//check last backup task whether complete or version whether exist
if db.GetManager().AppBackupDao().CheckHistory(b.Body.GroupID, b.Body.Version) {
return nil, util.CreateAPIHandleError(400, fmt.Errorf("last backup task do not complete"))
}
//check all service exist
if alias, err := db.GetManager().TenantServiceDao().GetServiceAliasByIDs(b.Body.ServiceIDs); len(alias) != len(b.Body.ServiceIDs) || err != nil {
return nil, util.CreateAPIHandleError(400, fmt.Errorf("some services do not exist in need backup services"))
}
//make source dir
sourceDir := fmt.Sprintf("/grdata/groupbackup/%s_%s", b.Body.GroupID, b.Body.Version)
if err := os.MkdirAll(sourceDir, 0755); err != nil {
return nil, util.CreateAPIHandleError(500, fmt.Errorf("create backup dir error,%s", err))
}
b.Body.SourceDir = sourceDir
appBackup.SourceDir = sourceDir
//snapshot the app metadata of region and write
if err := h.snapshot(b.Body.ServiceIDs, sourceDir); err != nil {
os.RemoveAll(sourceDir)
return nil, util.CreateAPIHandleError(500, fmt.Errorf("snapshot group apps error,%s", err))
}
logger.Info(core_util.Translation("write region level metadata success"), map[string]string{"step": "back-api"})
//write console level metadata.
if err := ioutil.WriteFile(fmt.Sprintf("%s/console_apps_metadata.json", sourceDir), []byte(b.Body.Metadata), 0755); err != nil {
return nil, util.CreateAPIHandleError(500, fmt.Errorf("write metadata file error,%s", err))
}
logger.Info(core_util.Translation("write console level metadata success"), map[string]string{"step": "back-api"})
//save backup history
if err := db.GetManager().AppBackupDao().AddModel(&appBackup); err != nil {
return nil, util.CreateAPIHandleErrorFromDBError("create backup history", err)
}
//clear metadata
b.Body.Metadata = ""
b.Body.BackupID = appBackup.BackupID
data, err := ffjson.Marshal(b.Body)
if err != nil {
return nil, util.CreateAPIHandleError(500, fmt.Errorf("build task body data error,%s", err))
}
//Initiate a data backup task.
task := &apidb.BuildTaskStruct{
TaskType: "backup_apps_new",
TaskBody: data,
}
eq, err := apidb.BuildTaskBuild(task)
if err != nil {
logrus.Error("Failed to BuildTaskBuild for BackupApp:", err)
return nil, util.CreateAPIHandleError(500, fmt.Errorf("build task error,%s", err))
}
// 写入事件到MQ中
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = h.MQClient.Enqueue(ctx, eq)
if err != nil {
logrus.Error("Failed to Enqueue MQ for BackupApp:", err)
return nil, util.CreateAPIHandleError(500, fmt.Errorf("build enqueue task error,%s", err))
}
logger.Info(core_util.Translation("Asynchronous tasks are sent successfully"), map[string]string{"step": "back-api"})
return &appBackup, nil
}
//GetBackup get one backup info
func (h *BackupHandle) GetBackup(backupID string) (*dbmodel.AppBackup, *util.APIHandleError) {
backup, err := db.GetManager().AppBackupDao().GetAppBackup(backupID)
if err != nil {
return nil, util.CreateAPIHandleErrorFromDBError("get backup history", err)
}
return backup, nil
}
//GetBackupByGroupID get some backup info by group id
func (h *BackupHandle) GetBackupByGroupID(groupID string) ([]*dbmodel.AppBackup, *util.APIHandleError) {
backups, err := db.GetManager().AppBackupDao().GetAppBackups(groupID)
if err != nil {
return nil, util.CreateAPIHandleErrorFromDBError("get backup history", err)
}
return backups, nil
}
//RegionServiceSnapshot RegionServiceSnapshot
type RegionServiceSnapshot struct {
ServiceID string
Service *dbmodel.TenantServices
ServiceProbe []*dbmodel.ServiceProbe
LBMappingPort []*dbmodel.TenantServiceLBMappingPort
ServiceEnv []*dbmodel.TenantServiceEnvVar
ServiceLabel []*dbmodel.TenantServiceLable
ServiceMntRelation []*dbmodel.TenantServiceMountRelation
PluginRelation []*dbmodel.TenantServicePluginRelation
ServiceRelation []*dbmodel.TenantServiceRelation
ServiceStatus string
ServiceVolume []*dbmodel.TenantServiceVolume
ServicePort []*dbmodel.TenantServicesPort
Versions []*dbmodel.VersionInfo
}
//snapshot
func (h *BackupHandle) snapshot(ids []string, sourceDir string) error {
var datas []RegionServiceSnapshot
for _, id := range ids {
var data = RegionServiceSnapshot{
ServiceID: id,
}
status := h.statusCli.GetStatus(id)
serviceType, err := db.GetManager().TenantServiceLabelDao().GetTenantServiceTypeLabel(id)
if err != nil {
return fmt.Errorf("Get service deploy type error,%s", err)
}
if status != client.CLOSED && serviceType.LabelValue == core_util.StatefulServiceType {
return fmt.Errorf("Statefulset app must be closed before backup,%s", err)
}
data.ServiceStatus = status
service, err := db.GetManager().TenantServiceDao().GetServiceByID(id)
if err != nil {
return fmt.Errorf("Get service(%s) error %s", id, err)
}
data.Service = service
serviceProbes, err := db.GetManager().ServiceProbeDao().GetServiceProbes(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) probe error %s", id, err)
}
data.ServiceProbe = serviceProbes
lbmappingPorts, err := db.GetManager().TenantServiceLBMappingPortDao().GetTenantServiceLBMappingPortByService(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) lb mapping port error %s", id, err)
}
data.LBMappingPort = lbmappingPorts
serviceEnv, err := db.GetManager().TenantServiceEnvVarDao().GetServiceEnvs(id, nil)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) envs error %s", id, err)
}
data.ServiceEnv = serviceEnv
serviceLabels, err := db.GetManager().TenantServiceLabelDao().GetTenantServiceLabel(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) labels error %s", id, err)
}
data.ServiceLabel = serviceLabels
serviceMntRelations, err := db.GetManager().TenantServiceMountRelationDao().GetTenantServiceMountRelationsByService(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) mnt relations error %s", id, err)
}
data.ServiceMntRelation = serviceMntRelations
servicePlugins, err := db.GetManager().TenantServicePluginRelationDao().GetALLRelationByServiceID(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) plugins error %s", id, err)
}
data.PluginRelation = servicePlugins
serviceRelations, err := db.GetManager().TenantServiceRelationDao().GetTenantServiceRelations(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) relations error %s", id, err)
}
data.ServiceRelation = serviceRelations
serviceVolume, err := db.GetManager().TenantServiceVolumeDao().GetTenantServiceVolumesByServiceID(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) volume error %s", id, err)
}
data.ServiceVolume = serviceVolume
servicePorts, err := db.GetManager().TenantServicesPortDao().GetPortsByServiceID(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) ports error %s", id, err)
}
data.ServicePort = servicePorts
versions, err := db.GetManager().VersionInfoDao().GetVersionByServiceID(id)
if err != nil && err.Error() != gorm.ErrRecordNotFound.Error() {
return fmt.Errorf("Get service(%s) build versions error %s", id, err)
}
data.Versions = versions
datas = append(datas, data)
}
body, err := ffjson.Marshal(datas)
if err != nil {
return err
}
//write region level metadata.
if err := ioutil.WriteFile(fmt.Sprintf("%s/region_apps_metadata.json", sourceDir), body, 0755); err != nil {
return util.CreateAPIHandleError(500, fmt.Errorf("write region_apps_metadata file error,%s", err))
}
return nil
}

View File

@ -21,13 +21,15 @@ package handler
import (
"time"
"github.com/goodrain/rainbond/api/handler/group"
"github.com/goodrain/rainbond/api/handler/share"
"github.com/Sirupsen/logrus"
"github.com/coreos/etcd/clientv3"
"github.com/goodrain/rainbond/cmd/api/option"
api_db "github.com/goodrain/rainbond/api/db"
"github.com/goodrain/rainbond/appruntimesync/client"
"github.com/goodrain/rainbond/cmd/api/option"
)
//InitHandle 初始化handle
@ -64,6 +66,7 @@ func InitHandle(conf option.Config, statusCli *client.AppRuntimeSyncClient) erro
defaultNetRulesHandler = CreateNetRulesManager(etcdCli)
defaultSourcesHandler = CreateSourcesManager(etcdCli)
defaultCloudHandler = CreateCloudManager(conf)
defaultAPPBackupHandler = group.CreateBackupHandle(mqClient, statusCli)
//需要使用etcd v2 API
defaultEventHandler = CreateLogManager(conf.EtcdEndpoint)
shareHandler = &share.ServiceShareHandle{MQClient: mqClient, EtcdCli: etcdCli}
@ -131,6 +134,14 @@ func GetEventHandler() EventHandler {
var defaultAppHandler *AppAction
//GetAppHandler GetAppHandler
func GetAppHandler() *AppAction {
return defaultAppHandler
}
}
var defaultAPPBackupHandler *group.BackupHandle
//GetAPPBackupHandler GetAPPBackupHandler
func GetAPPBackupHandler() *group.BackupHandle {
return defaultAPPBackupHandler
}

View File

@ -353,7 +353,6 @@ func (i *SourceCodeBuildItem) buildCode() error {
CommitMsg: i.commit.Message,
Author: i.commit.Author.Name,
}
logrus.Debugf("update app version commit info %s, author %s", i.commit.Message, i.commit.Author.Name)
if err := i.UpdateVersionInfo(vi); err != nil {
logrus.Errorf("update version info error: %s", err.Error())
i.Logger.Error("更新应用版本信息失败", map[string]string{"step": "build-code", "status": "failure"})
@ -392,6 +391,7 @@ func (i *SourceCodeBuildItem) UpdateVersionInfo(vi *dbmodel.VersionInfo) error {
version.CommitMsg = vi.CommitMsg
version.Author = vi.Author
version.CodeVersion = vi.CodeVersion
logrus.Debugf("update app version %+v", *version)
if err := db.GetManager().VersionInfoDao().UpdateModel(version); err != nil {
return err
}

View File

@ -78,12 +78,14 @@ type TaskWorker interface {
GetLogger() event.Logger
Name() string
Stop() error
//ErrorCallBack if run error will callback
ErrorCallBack(err error)
}
var workerCreaterList = make(map[string]func([]byte) TaskWorker)
var workerCreaterList = make(map[string]func([]byte) (TaskWorker, error))
//RegisterWorker register worker creater
func RegisterWorker(name string, fun func([]byte) TaskWorker) {
func RegisterWorker(name string, fun func([]byte) (TaskWorker, error)) {
workerCreaterList[name] = fun
}
@ -126,7 +128,11 @@ func (e *exectorManager) exec(workerName string, in []byte) error {
if !ok {
return fmt.Errorf("`%s` tasktype can't support", workerName)
}
worker := creater(in)
worker, err := creater(in)
if err != nil {
logrus.Errorf("create worker for builder error.%s", err)
return err
}
go func() {
defer event.GetManager().ReleaseLogger(worker.GetLogger())
defer func() {
@ -136,7 +142,9 @@ func (e *exectorManager) exec(workerName string, in []byte) error {
worker.GetLogger().Error("后端服务开小差,请重试或联系客服", map[string]string{"step": "callback", "status": "failure"})
}
}()
worker.Run(time.Minute * 10)
if err := worker.Run(time.Minute * 10); err != nil {
worker.ErrorCallBack(err)
}
}()
return nil
}

View File

@ -22,6 +22,13 @@ import (
"time"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
@ -32,12 +39,6 @@ import (
"github.com/pkg/errors"
"github.com/tidwall/gjson"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"os/exec"
"path"
"strconv"
"strings"
)
//ExportApp Export app to specified format(rainbond-app or dockercompose)
@ -54,11 +55,11 @@ func init() {
}
//NewExportApp create
func NewExportApp(in []byte) TaskWorker {
func NewExportApp(in []byte) (TaskWorker, error) {
dockerClient, err := client.NewEnvClient()
if err != nil {
logrus.Error("Failed to create task for export app: ", err)
return nil
return nil, err
}
eventID := gjson.GetBytes(in, "event_id").String()
@ -69,7 +70,7 @@ func NewExportApp(in []byte) TaskWorker {
Logger: logger,
EventID: eventID,
DockerClient: dockerClient,
}
}, nil
}
//Run Run
@ -86,10 +87,8 @@ func (i *ExportApp) Run(timeout time.Duration) error {
i.updateStatus("failed")
}
return err
} else {
return errors.New("Unsupported the format: " + i.Format)
}
return nil
return errors.New("Unsupported the format: " + i.Format)
}
// 组目录命名规则将组名中unicode转为中文并去掉空格"JAVA-ETCD\\u5206\\u4eab\\u7ec4" -> "JAVA-ETCD分享组"
@ -592,6 +591,11 @@ func (i *ExportApp) buildStartScript() error {
return nil
}
//ErrorCallBack if run error will callback
func (i *ExportApp) ErrorCallBack(err error) {
}
func (i *ExportApp) zip() error {
// /grdata/export-app/myapp-1.0 -> /grdata/export-app
dirName := path.Dir(i.SourceDir)

View File

@ -0,0 +1,268 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package exector
import (
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/goodrain/rainbond/builder/sources"
"github.com/goodrain/rainbond/util"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
dbmodel "github.com/goodrain/rainbond/db/model"
"github.com/goodrain/rainbond/event"
"github.com/pquerna/ffjson/ffjson"
"github.com/tidwall/gjson"
)
//BackupAPPNew backup group app new version
type BackupAPPNew struct {
GroupID string `json:"group_id" `
ServiceIDs []string `json:"service_ids" `
//full-online,full-offline
Mode string `json:"mode"`
Version string `json:"version"`
EventID string
SlugInfo struct {
FTPNamespace string `json:"ftp_namespace"`
FTPHost string `json:"ftp_host"`
FTPPort string `json:"ftp_port"`
FTPUser string `json:"ftp_username"`
FTPPassword string `json:"ftp_password"`
} `json:"slug_info"`
ImageInfo struct {
HubURL string `json:"hub_url"`
HubUser string `json:"hub_user"`
HubPassword string `json:"hub_password"`
Namespace string `json:"namespace"`
IsTrust bool `json:"is_trust,omitempty"`
} `json:"image_info,omitempty"`
SourceDir string `json:"source_dir"`
BackupID string `json:"backup_id"`
BackupSize int64
Logger event.Logger
DockerClient *client.Client
}
func init() {
RegisterWorker("backup_apps_new", BackupAPPNewCreater)
}
//BackupAPPNewCreater create
func BackupAPPNewCreater(in []byte) (TaskWorker, error) {
dockerClient, err := client.NewEnvClient()
if err != nil {
logrus.Error("Failed to create task for export app: ", err)
return nil, err
}
eventID := gjson.GetBytes(in, "event_id").String()
logger := event.GetManager().GetLogger(eventID)
backupNew := &BackupAPPNew{
Logger: logger,
EventID: eventID,
DockerClient: dockerClient,
}
if err := ffjson.Unmarshal(in, &backupNew); err != nil {
return nil, err
}
return backupNew, nil
}
//RegionServiceSnapshot RegionServiceSnapshot
type RegionServiceSnapshot struct {
ServiceID string
Service *dbmodel.TenantServices
ServiceProbe []*dbmodel.ServiceProbe
LBMappingPort []*dbmodel.TenantServiceLBMappingPort
ServiceEnv []*dbmodel.TenantServiceEnvVar
ServiceLabel []*dbmodel.TenantServiceLable
ServiceMntRelation []*dbmodel.TenantServiceMountRelation
PluginRelation []*dbmodel.TenantServicePluginRelation
ServiceRelation []*dbmodel.TenantServiceRelation
ServiceStatus string
ServiceVolume []*dbmodel.TenantServiceVolume
ServicePort []*dbmodel.TenantServicesPort
Versions []*dbmodel.VersionInfo
}
//Run Run
func (b *BackupAPPNew) Run(timeout time.Duration) error {
//read region group app metadata
metadata, err := ioutil.ReadFile(fmt.Sprintf("%s/region_apps_metadata.json", b.SourceDir))
if err != nil {
return err
}
var appSnapshots []*RegionServiceSnapshot
if err := ffjson.Unmarshal(metadata, &appSnapshots); err != nil {
return err
}
for _, app := range appSnapshots {
//backup app image or code slug file
b.Logger.Info(fmt.Sprintf("开始备份应用(%s)运行环境", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "starting"})
for i := range app.Versions {
if version := app.Versions[len(app.Versions)-1-i]; version != nil && version.BuildVersion == app.Service.DeployVersion {
if version.DeliveredType == "slug" {
if err := b.uploadSlug(app, version); err != nil {
logrus.Errorf("upload app slug file error.%s", err.Error())
return err
}
}
if version.DeliveredType == "image" {
if err := b.uploadImage(app, version); err != nil {
logrus.Errorf("upload app image error.%s", err.Error())
return err
}
}
break
}
}
b.Logger.Info(fmt.Sprintf("完成备份应用(%s)运行环境", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "success"})
b.Logger.Info(fmt.Sprintf("开始备份应用(%s)持久化数据", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "starting"})
//backup app data
for _, volume := range app.ServiceVolume {
if volume.HostPath != "" && !util.DirIsEmpty(volume.HostPath) {
dstDir := fmt.Sprintf("%s/data_%s/%s.zip", b.SourceDir, app.ServiceID, strings.Replace(volume.VolumeName, "/", "", -1))
if err := util.Zip(volume.HostPath, dstDir); err != nil {
logrus.Errorf("backup service(%s) volume(%s) data error.%s", app.ServiceID, volume.VolumeName, err.Error())
return err
}
}
}
b.Logger.Info(fmt.Sprintf("完成备份应用(%s)持久化数据", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "success"})
//TODO:backup relation volume data?
}
//upload app backup data to online server(sftp) if mode is full-online
if b.Mode == "full-online" && b.SlugInfo.FTPHost != "" && b.SlugInfo.FTPPort != "" {
b.Logger.Info(fmt.Sprintf("开始上传备份元数据到云端"), map[string]string{"step": "backup_builder", "status": "starting"})
if err := util.CheckAndCreateDir("/grdata/tmp"); err != nil {
return err
}
if err := util.Zip(b.SourceDir, fmt.Sprintf("/grdata/tmp/%s_%s.zip", b.GroupID, b.Version)); err != nil {
b.Logger.Info(fmt.Sprintf("压缩备份元数据失败"), map[string]string{"step": "backup_builder", "status": "starting"})
return err
}
sFTPClient, err := sources.NewSFTPClient(b.SlugInfo.FTPUser, b.SlugInfo.FTPPassword, b.SlugInfo.FTPHost, b.SlugInfo.FTPPort)
if err != nil {
b.Logger.Error(util.Translation("create ftp client error"), map[string]string{"step": "backup_builder", "status": "failure"})
return err
}
defer sFTPClient.Close()
dstDir := fmt.Sprintf("%s/backup/%s_%s/metadata_data.zip", b.SlugInfo.FTPNamespace, b.GroupID, b.Version)
if err := sFTPClient.PushFile(fmt.Sprintf("/grdata/tmp/%s_%s.zip", b.GroupID, b.Version), dstDir, b.Logger); err != nil {
b.Logger.Error(util.Translation("push slug file to sftp server error"), map[string]string{"step": "backup_builder", "status": "failure"})
logrus.Errorf("push slug file error when backup app , %s", err.Error())
return err
}
//Statistical backup size
b.BackupSize += util.GetFileSize(fmt.Sprintf("/grdata/tmp/%s_%s.zip", b.GroupID, b.Version))
os.Remove(fmt.Sprintf("/grdata/tmp/%s_%s.zip", b.GroupID, b.Version))
}
return nil
}
func (b *BackupAPPNew) uploadSlug(app *RegionServiceSnapshot, version *dbmodel.VersionInfo) error {
if b.Mode == "full-online" && b.SlugInfo.FTPHost != "" && b.SlugInfo.FTPPort != "" {
sFTPClient, err := sources.NewSFTPClient(b.SlugInfo.FTPUser, b.SlugInfo.FTPPassword, b.SlugInfo.FTPHost, b.SlugInfo.FTPPort)
if err != nil {
b.Logger.Error(util.Translation("create ftp client error"), map[string]string{"step": "backup_builder", "status": "failure"})
return err
}
defer sFTPClient.Close()
dstDir := fmt.Sprintf("%s/backup/%s_%s/app_%s/%s.tgz", b.SlugInfo.FTPNamespace, b.GroupID, b.Version, app.ServiceID, version.BuildVersion)
if err := sFTPClient.PushFile(version.DeliveredPath, dstDir, b.Logger); err != nil {
b.Logger.Error(util.Translation("push slug file to sftp server error"), map[string]string{"step": "backup_builder", "status": "failure"})
logrus.Errorf("push slug file error when backup app , %s", err.Error())
return err
}
//Statistical backup size
b.BackupSize += util.GetFileSize(version.DeliveredPath)
} else {
dstDir := fmt.Sprintf("%s/app_%s/slug_%s.tgz", b.SourceDir, app.ServiceID, version.BuildVersion)
if err := sources.CopyFileWithProgress(version.DeliveredPath, dstDir, b.Logger); err != nil {
b.Logger.Error(util.Translation("push slug file to local dir error"), map[string]string{"step": "backup_builder", "status": "failure"})
logrus.Errorf("copy slug file error when backup app, %s", err.Error())
return err
}
}
return nil
}
func (b *BackupAPPNew) uploadImage(app *RegionServiceSnapshot, version *dbmodel.VersionInfo) error {
if b.Mode == "full-online" && b.ImageInfo.HubURL != "" {
backupImage, err := app.Service.CreateShareImage(b.ImageInfo.HubURL, b.ImageInfo.Namespace, fmt.Sprintf("%s_backup", b.Version))
if err != nil {
return fmt.Errorf("create backup image error %s", err)
}
info, err := sources.ImagePull(b.DockerClient, version.ImageName, types.ImagePullOptions{}, b.Logger, 10)
if err != nil {
return fmt.Errorf("pull image when backup error %s", err)
}
if err := sources.ImageTag(b.DockerClient, version.ImageName, backupImage, b.Logger, 1); err != nil {
return fmt.Errorf("change image tag when backup error %s", err)
}
if b.ImageInfo.IsTrust {
if err := sources.TrustedImagePush(b.DockerClient, backupImage, b.ImageInfo.HubUser, b.ImageInfo.HubPassword, b.Logger, 10); err != nil {
b.Logger.Error(util.Translation("save image to hub error"), map[string]string{"step": "backup_builder", "status": "failure"})
return fmt.Errorf("backup image push error %s", err)
}
} else {
if err := sources.ImagePush(b.DockerClient, backupImage, b.ImageInfo.HubUser, b.ImageInfo.HubPassword, b.Logger, 10); err != nil {
b.Logger.Error(util.Translation("save image to hub error"), map[string]string{"step": "backup_builder", "status": "failure"})
return fmt.Errorf("backup image push error %s", err)
}
}
b.BackupSize += info.Size
} else {
dstDir := fmt.Sprintf("%s/app_%s/image_%s.tar", b.SourceDir, app.ServiceID, version.BuildVersion)
if err := sources.ImageSave(b.DockerClient, version.ImageName, dstDir, b.Logger); err != nil {
b.Logger.Error(util.Translation("save image to local dir error"), map[string]string{"step": "backup_builder", "status": "failure"})
logrus.Errorf("save image to local dir error when backup app, %s", err.Error())
return err
}
}
return nil
}
//Stop stop
func (b *BackupAPPNew) Stop() error {
return nil
}
//Name return worker name
func (b *BackupAPPNew) Name() string {
return "backup_apps_new"
}
//GetLogger GetLogger
func (b *BackupAPPNew) GetLogger() event.Logger {
return b.Logger
}
//ErrorCallBack if run error will callback
func (b *BackupAPPNew) ErrorCallBack(err error) {
if err != nil {
logrus.Errorf("backup group app failure %s", err)
b.Logger.Error(util.Translation("backup group app failure"), map[string]string{"step": "callback", "status": "failure"})
}
}

View File

@ -19,20 +19,21 @@
package exector
import (
"errors"
"fmt"
"github.com/Sirupsen/logrus"
"io/ioutil"
"os/exec"
"strings"
"path/filepath"
"github.com/goodrain/rainbond/builder/sources"
"github.com/tidwall/gjson"
"github.com/goodrain/rainbond/event"
"github.com/docker/engine-api/client"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/client"
"github.com/goodrain/rainbond/builder/sources"
"github.com/goodrain/rainbond/db"
"errors"
"github.com/goodrain/rainbond/db/model"
"github.com/goodrain/rainbond/event"
"github.com/tidwall/gjson"
)
func init() {
@ -49,11 +50,11 @@ type ImportApp struct {
}
//NewExportApp create
func NewImportApp(in []byte) TaskWorker {
func NewImportApp(in []byte) (TaskWorker, error) {
dockerClient, err := client.NewEnvClient()
if err != nil {
logrus.Error("Failed to create task for export app: ", err)
return nil
return nil, err
}
eventID := gjson.GetBytes(in, "event_id").String()
@ -64,7 +65,7 @@ func NewImportApp(in []byte) TaskWorker {
Logger: logger,
EventID: eventID,
DockerClient: dockerClient,
}
}, nil
}
//Stop stop
@ -90,10 +91,8 @@ func (i *ImportApp) Run(timeout time.Duration) error {
i.updateStatus("failed")
}
return err
} else {
return errors.New("Unsupported the format: " + i.Format)
}
return nil
return errors.New("Unsupported the format: " + i.Format)
}
// 组目录命名规则将组名中unicode转为中文并去掉空格"JAVA-ETCD\\u5206\\u4eab\\u7ec4" -> "JAVA-ETCD分享组"
@ -227,6 +226,11 @@ func (i *ImportApp) loadApps() error {
return nil
}
//ErrorCallBack if run error will callback
func (i *ImportApp) ErrorCallBack(err error) {
}
func (i *ImportApp) updateStatus(status string) error {
logrus.Debug("Update app status in database to: ", status)
// 从数据库中获取该应用的状态信息
@ -249,7 +253,7 @@ func (i *ImportApp) updateStatus(status string) error {
app.Metadata = string(data)
if err := db.GetManager().AppDao().UpdateModel(app); err != nil {
err = errors.New(fmt.Sprintf("Failed to update app %s: %v", i.EventID, err))
err = fmt.Errorf("Failed to update app %s: %v", i.EventID, err)
logrus.Error(err)
return err
}

View File

@ -113,23 +113,13 @@ func createMD5(packageName string) (string, error) {
//ShareToFTP ShareToFTP
func (i *SlugShareItem) ShareToFTP() error {
file := i.LocalSlugPath
i.Logger.Info("开始上传应用介质到FTP服务器", map[string]string{"step": "slug-share"})
md5, err := createMD5(file)
if err != nil {
i.Logger.Error("生成md5失败", map[string]string{"step": "slug-share", "status": "failure"})
return err
}
sFTPClient, err := sources.NewSFTPClient(i.ShareInfo.SlugInfo.FTPUser, i.ShareInfo.SlugInfo.FTPPassword, i.ShareInfo.SlugInfo.FTPHost, i.ShareInfo.SlugInfo.FTPPort)
if err != nil {
i.Logger.Error("创建FTP客户端失败", map[string]string{"step": "slug-share", "status": "failure"})
return err
}
defer sFTPClient.Close()
if err := sFTPClient.PushFile(md5, i.SlugPath+".md5", i.Logger); err != nil {
i.Logger.Error("上传MD5文件失败", map[string]string{"step": "slug-share", "status": "failure"})
return err
}
if err := sFTPClient.PushFile(i.LocalSlugPath, i.SlugPath, i.Logger); err != nil {
i.Logger.Error("上传源码包文件失败", map[string]string{"step": "slug-share", "status": "failure"})
return err

View File

@ -20,6 +20,7 @@ package sources
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
@ -97,11 +98,48 @@ func (s *SFTPClient) Close() {
s.conn.Close()
}
}
func (s *SFTPClient) checkMd5(src, dst string, logger event.Logger) (bool, error) {
if err := util.CreateFileHash(src, src+".md5"); err != nil {
return false, err
}
existmd5, err := s.FileExist(dst + ".md5")
if err != nil {
return false, err
}
exist, err := s.FileExist(dst)
if err != nil {
return false, err
}
if exist && existmd5 {
if err := s.DownloadFile(dst+".md5", src+".md5.old", logger); err != nil {
return false, err
}
old, err := ioutil.ReadFile(src + ".md5.old")
if err != nil {
return false, err
}
os.Remove(src + ".md5.old")
new, err := ioutil.ReadFile(src + ".md5")
if err != nil {
return false, err
}
if string(old) == string(new) {
return true, nil
}
}
return false, nil
}
//PushFile PushFile
func (s *SFTPClient) PushFile(src, dst string, logger event.Logger) error {
logger.Info(fmt.Sprintf("开始上传代码包到FTP服务器"), map[string]string{"step": "slug-share"})
ok, err := s.checkMd5(src, dst, logger)
if err != nil {
return err
}
if ok {
return nil
}
srcFile, err := os.OpenFile(src, os.O_RDONLY, 0644)
if err != nil {
if logger != nil {
@ -117,7 +155,7 @@ func (s *SFTPClient) PushFile(src, dst string, logger event.Logger) error {
}
return err
}
// 验证并创建目标目录
// check or create dir
dir := filepath.Dir(dst)
_, err = s.sftpClient.Stat(dir)
if err != nil {
@ -136,7 +174,7 @@ func (s *SFTPClient) PushFile(src, dst string, logger event.Logger) error {
return err
}
}
// 先删除文件如果存在
// remove all file if exist
s.sftpClient.Remove(dst)
dstFile, err := s.sftpClient.Create(dst)
if err != nil {
@ -147,7 +185,21 @@ func (s *SFTPClient) PushFile(src, dst string, logger event.Logger) error {
}
defer dstFile.Close()
allSize := srcStat.Size()
return CopyWithProgress(srcFile, dstFile, allSize, logger)
if err := CopyWithProgress(srcFile, dstFile, allSize, logger); err != nil {
return err
}
// write remote md5 file
md5, _ := ioutil.ReadFile(src + ".md5")
dstMd5File, err := s.sftpClient.Create(dst + ".md5")
if err != nil {
logrus.Errorf("create md5 file in sftp server error.%s", err.Error())
return nil
}
defer dstMd5File.Close()
if _, err := dstMd5File.Write(md5); err != nil {
logrus.Errorf("write md5 file in sftp server error.%s", err.Error())
}
return nil
}
//DownloadFile DownloadFile

View File

@ -222,7 +222,7 @@ type TenantServiceVolumeDao interface {
type TenantServiceLBMappingPortDao interface {
Dao
GetTenantServiceLBMappingPort(serviceID string, containerPort int) (*model.TenantServiceLBMappingPort, error)
GetTenantServiceLBMappingPortByService(serviceID string) (*model.TenantServiceLBMappingPort, error)
GetTenantServiceLBMappingPortByService(serviceID string) ([]*model.TenantServiceLBMappingPort, error)
CreateTenantServiceLBMappingPort(serviceID string, containerPort int) (*model.TenantServiceLBMappingPort, error)
DELServiceLBMappingPortByServiceID(serviceID string) error
}
@ -363,3 +363,12 @@ type NotificationEventDao interface {
GetNotificationEventByTime(start, end time.Time) ([]*model.NotificationEvent, error)
GetNotificationEventNotHandle() ([]*model.NotificationEvent, error)
}
//AppBackupDao group app backup history
type AppBackupDao interface {
Dao
CheckHistory(groupID, version string) bool
GetAppBackups(groupID string) ([]*model.AppBackup, error)
DeleteAppBackup(backupID string) error
GetAppBackup(backupID string) (*model.AppBackup, error)
}

View File

@ -100,6 +100,7 @@ type Manager interface {
RegionProcotolsDao() dao.RegionProcotolsDao
NotificationEventDao() dao.NotificationEventDao
AppBackupDao() dao.AppBackupDao
}
var defaultManager Manager

View File

@ -13,3 +13,22 @@ type AppStatus struct {
func (t *AppStatus) TableName() string {
return "app_status"
}
//AppBackup app backup info
type AppBackup struct {
Model
EventID string `gorm:"column:event_id;size:32;" json:"event_id"`
BackupID string `gorm:"column:backup_id;size:32;" json:"backup_id"`
GroupID string `gorm:"column:group_id;size:32;" json:"group_id"`
//Status in starting,failed,success
Status string `gorm:"column:status;size:32" json:"status"`
Version string `gorm:"column:version;size:32" json:"version"`
SourceDir string `gorm:"column:source_dir;size:255" json:"source_dir"`
BackupMode string `gorm:"column:backup_mode;size:32" json:"backup_mode"`
BuckupSize int `gorm:"column:backup_size" json:"backup_size"`
}
//TableName 表名
func (t *AppBackup) TableName() string {
return "app_backup"
}

View File

@ -1,8 +1,10 @@
package dao
import (
"github.com/jinzhu/gorm"
"fmt"
"github.com/goodrain/rainbond/db/model"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
)
@ -52,3 +54,69 @@ func (a *AppDaoImpl) GetByEventId(eventId string) (interface{}, error) {
return &app, err
}
//AppBackupDaoImpl group app backup info store mysql impl
type AppBackupDaoImpl struct {
DB *gorm.DB
}
//AddModel AddModel
func (a *AppBackupDaoImpl) AddModel(mo model.Interface) error {
app, ok := mo.(*model.AppBackup)
if !ok {
return errors.New("Failed to convert interface to AppStatus")
}
var old model.AppBackup
if ok := a.DB.Where("backup_id = ?", app.BackupID).Find(&old).RecordNotFound(); ok {
if err := a.DB.Create(app).Error; err != nil {
return err
}
}
return fmt.Errorf("backup info exist with id %s", app.BackupID)
}
//UpdateModel UpdateModel
func (a *AppBackupDaoImpl) UpdateModel(mo model.Interface) error {
app, ok := mo.(*model.AppBackup)
if !ok {
return errors.New("Failed to convert interface to AppStatus")
}
if app.ID == 0 {
return errors.New("Primary id can not be 0 when update")
}
return a.DB.Where("backup_id = ?", app.BackupID).Update(app).Error
}
//CheckHistory CheckHistory
func (a *AppBackupDaoImpl) CheckHistory(groupID, version string) bool {
var app model.AppBackup
return a.DB.Where("(group_id = ? and status =?) or version=? ", groupID, "starting", version).Find(&app).RecordNotFound()
}
//GetAppBackups GetAppBackups
func (a *AppBackupDaoImpl) GetAppBackups(groupID string) ([]*model.AppBackup, error) {
var apps []*model.AppBackup
if err := a.DB.Where("group_id = ?", groupID).Find(&apps).Error; err != nil {
return nil, err
}
return apps, nil
}
//DeleteAppBackup DeleteAppBackup
func (a *AppBackupDaoImpl) DeleteAppBackup(backupID string) error {
var app model.AppBackup
if err := a.DB.Where("backup_id = ?", backupID).Delete(&app).Error; err != nil {
return err
}
return nil
}
//GetAppBackup GetAppBackup
func (a *AppBackupDaoImpl) GetAppBackup(backupID string) (*model.AppBackup, error) {
var app model.AppBackup
if err := a.DB.Where("backup_id = ?", backupID).Find(&app).Error; err != nil {
return nil, err
}
return &app, nil
}

View File

@ -971,12 +971,12 @@ func (t *TenantServiceLBMappingPortDaoImpl) CreateTenantServiceLBMappingPort(ser
}
//GetTenantServiceLBMappingPortByService 获取端口映射
func (t *TenantServiceLBMappingPortDaoImpl) GetTenantServiceLBMappingPortByService(serviceID string) (*model.TenantServiceLBMappingPort, error) {
var mapPort model.TenantServiceLBMappingPort
func (t *TenantServiceLBMappingPortDaoImpl) GetTenantServiceLBMappingPortByService(serviceID string) ([]*model.TenantServiceLBMappingPort, error) {
var mapPort []*model.TenantServiceLBMappingPort
if err := t.DB.Where("service_id=?", serviceID).Find(&mapPort).Error; err != nil {
return nil, err
}
return &mapPort, nil
return mapPort, nil
}
//DELServiceLBMappingPortByServiceID DELServiceLBMappingPortByServiceID

View File

@ -451,3 +451,10 @@ func (m *Manager) AppDao() dao.AppDao {
DB: m.db,
}
}
//AppBackupDao group app backup info
func (m *Manager) AppBackupDao() dao.AppBackupDao {
return &mysqldao.AppBackupDaoImpl{
DB: m.db,
}
}

View File

@ -117,6 +117,7 @@ func (m *Manager) RegisterTableModel() {
m.models = append(m.models, &model.LocalScheduler{})
m.models = append(m.models, &model.NotificationEvent{})
m.models = append(m.models, &model.AppStatus{})
m.models = append(m.models, &model.AppBackup{})
}
//CheckTable check and create tables

View File

@ -19,7 +19,9 @@
package util
import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"net"
"os"
@ -281,6 +283,14 @@ func GetDirSizeByCmd(path string) float64 {
return float64(i)
}
//GetFileSize get file size
func GetFileSize(path string) int64 {
if fileInfo, err := os.Stat(path); err == nil {
return fileInfo.Size()
}
return 0
}
//GetDirSize kb为单位
func GetDirSize(path string) float64 {
if ok, err := FileExists(path); err != nil || !ok {
@ -364,3 +374,109 @@ func CmdExec(args string) (string, error) {
}
return string(out), nil
}
//Zip zip compressing source dir to target file
func Zip(source, target string) error {
zipfile, err := os.Create(target)
if err != nil {
return err
}
defer zipfile.Close()
archive := zip.NewWriter(zipfile)
defer archive.Close()
info, err := os.Stat(source)
if err != nil {
return nil
}
var baseDir string
if info.IsDir() {
baseDir = filepath.Base(source)
}
filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
if baseDir != "" {
header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
}
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
})
return err
}
//Unzip archive file to target dir
func Unzip(archive, target string) error {
reader, err := zip.OpenReader(archive)
if err != nil {
return err
}
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
for _, file := range reader.File {
run := func() error {
path := filepath.Join(target, file.Name)
if file.FileInfo().IsDir() {
os.MkdirAll(path, file.Mode())
return nil
}
fileReader, err := file.Open()
if err != nil {
return err
}
defer fileReader.Close()
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
defer targetFile.Close()
if _, err := io.Copy(targetFile, fileReader); err != nil {
return err
}
return nil
}
if err := run(); err != nil {
return err
}
}
return nil
}

View File

@ -48,3 +48,15 @@ func TestGetDirSizeByCmd(t *testing.T) {
memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats)
}
func TestZip(t *testing.T) {
if err := Zip("/Users/qingguo/gopath/src/github.com/goodrain/rainbond", "/tmp/rainbond.zip"); err != nil {
t.Fatal(err)
}
}
func TestUnzip(t *testing.T) {
if err := Unzip("/tmp/rainbond.zip", "/tmp/rainbond"); err != nil {
t.Fatal(err)
}
}

90
util/hash.go Normal file
View File

@ -0,0 +1,90 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package util
import (
"crypto/md5"
"fmt"
"io"
"math"
"os"
)
const filechunk = 8192 // we settle for 8KB
//CreateFileHash compute sourcefile hash and write hashfile
func CreateFileHash(sourceFile, hashfile string) error {
file, err := os.Open(sourceFile)
if err != nil {
return err
}
defer file.Close()
fileinfo, _ := file.Stat()
if fileinfo.IsDir() {
return fmt.Errorf("do not support compute folder hash")
}
writehashfile, err := os.OpenFile(hashfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0655)
if err != nil {
return fmt.Errorf("create hash file error %s", err.Error())
}
defer writehashfile.Close()
if fileinfo.Size() < filechunk {
return createSmallFileHash(file, writehashfile)
}
return createBigFileHash(file, writehashfile)
}
func createBigFileHash(sourceFile, hashfile *os.File) error {
// calculate the file size
info, _ := sourceFile.Stat()
filesize := info.Size()
blocks := uint64(math.Ceil(float64(filesize) / float64(filechunk)))
hash := md5.New()
for i := uint64(0); i < blocks; i++ {
blocksize := int(math.Min(filechunk, float64(filesize-int64(i*filechunk))))
buf := make([]byte, blocksize)
index, err := sourceFile.Read(buf)
if err != nil {
return err
}
// append into the hash
_, err = hash.Write(buf[:index])
if err != nil {
return err
}
}
_, err := hashfile.Write([]byte(fmt.Sprintf("%x", hash.Sum(nil))))
if err != nil {
return err
}
return nil
}
func createSmallFileHash(sourceFile, hashfile *os.File) error {
md5h := md5.New()
_, err := io.Copy(md5h, sourceFile)
if err != nil {
return err
}
_, err = hashfile.Write([]byte(fmt.Sprintf("%x", md5h.Sum(nil))))
if err != nil {
return err
}
return nil
}

27
util/hash_test.go Normal file
View File

@ -0,0 +1,27 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package util
import "testing"
func TestCreateFileHash(t *testing.T) {
if err := CreateFileHash("/tmp/hashtest", "/tmp/hashtest.md5"); err != nil {
t.Fatal(err)
}
}

38
util/language.go Normal file
View File

@ -0,0 +1,38 @@
// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package util
var translationMetadata = map[string]string{
"write console level metadata success": "写控制台级应用元数据成功",
"write region level metadata success": "写数据中心级应用元数据成功",
"Asynchronous tasks are sent successfully": "异步任务发送成功",
"create ftp client error": "创建FTP客户端失败",
"push slug file to local dir error": "上传应用代码包到本地目录失败",
"push slug file to sftp server error": "上传应用代码包到服务端失败",
"save image to local dir error": "保存镜像到本地目录失败",
"save image to hub error": "保存镜像到仓库失败",
}
//Translation Translation English to Chinese
func Translation(english string) string {
if chinese, ok := translationMetadata[english]; ok {
return chinese
}
return english
}

View File

@ -220,7 +220,7 @@ func (k *K8sServiceBuild) createOuterService(port *model.TenantServicesPort) *v1
}
//if port.Protocol == "stream" { //stream 协议获取映射端口
if port.Protocol != "http" { //stream 协议获取映射端口
mapPort, err := k.dbmanager.TenantServiceLBMappingPortDao().GetTenantServiceLBMappingPortByService(k.serviceID)
mapPort, err := k.dbmanager.TenantServiceLBMappingPortDao().GetTenantServiceLBMappingPort(k.serviceID, port.ContainerPort)
if err != nil {
logrus.Error("get tenant service lb map port error", err.Error())
service.Labels["lbmap_port"] = "0"