mirror of
https://gitee.com/rainbond/Rainbond.git
synced 2024-11-29 18:27:58 +08:00
[ADD] add group app backup feature (#72)
This commit is contained in:
parent
97c5527458
commit
8cd2dc9697
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
75
api/controller/group.go
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
276
api/handler/group/group_backup.go
Normal file
276
api/handler/group/group_backup.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
268
builder/exector/groupapp_backup.go
Normal file
268
builder/exector/groupapp_backup.go
Normal 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"})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
1
db/db.go
1
db/db.go
@ -100,6 +100,7 @@ type Manager interface {
|
||||
RegionProcotolsDao() dao.RegionProcotolsDao
|
||||
|
||||
NotificationEventDao() dao.NotificationEventDao
|
||||
AppBackupDao() dao.AppBackupDao
|
||||
}
|
||||
|
||||
var defaultManager Manager
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
116
util/comman.go
116
util/comman.go
@ -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
|
||||
}
|
||||
|
@ -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
90
util/hash.go
Normal 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
27
util/hash_test.go
Normal 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
38
util/language.go
Normal 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
|
||||
}
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user