// 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 . package exector import ( "fmt" "io/ioutil" "os" "path" "path/filepath" "strings" "time" "github.com/goodrain/rainbond/util" "github.com/goodrain/rainbond/builder/sources" "github.com/goodrain/rainbond/builder/sources/registry" "github.com/goodrain/rainbond/db" "github.com/Sirupsen/logrus" "github.com/docker/docker/client" "github.com/goodrain/rainbond/builder/cloudos" dbmodel "github.com/goodrain/rainbond/db/model" "github.com/goodrain/rainbond/event" "github.com/pquerna/ffjson/ffjson" "github.com/tidwall/gjson" ) const ( // OldMetadata identify older versions of metadata OldMetadata = "OldMetadata" // NewMetadata identify new version of metadata NewMetadata = "NewMetadata" ) //maxBackupVersionSize Maximum number of backup versions per service var maxBackupVersionSize = 3 //BackupAPPNew backup group app new version type BackupAPPNew struct { GroupID string `json:"group_id" ` ServiceIDs []string `json:"service_ids" ` Version string `json:"version"` EventID string SourceDir string `json:"source_dir"` SourceType string `json:"source_type"` BackupID string `json:"backup_id"` BackupSize int64 Logger event.Logger DockerClient *client.Client //full-online,full-offline Mode string `json:"mode"` S3Config struct { Provider string `json:"provider"` Endpoint string `json:"endpoint"` AccessKey string `json:"access_key"` SecretKey string `json:"secret_key"` BucketName string `json:"bucket_name"` } `json:"s3_config"` } func init() { RegisterWorker("backup_apps_new", BackupAPPNewCreater) } //BackupAPPNewCreater create func BackupAPPNewCreater(in []byte, m *exectorManager) (TaskWorker, error) { eventID := gjson.GetBytes(in, "event_id").String() logger := event.GetManager().GetLogger(eventID) backupNew := &BackupAPPNew{ Logger: logger, EventID: eventID, DockerClient: m.DockerClient, } if err := ffjson.Unmarshal(in, &backupNew); err != nil { return nil, err } return backupNew, nil } // AppSnapshot holds a snapshot of your app type AppSnapshot struct { Services []*RegionServiceSnapshot Plugins []*dbmodel.TenantPlugin PluginBuildVersions []*dbmodel.TenantPluginBuildVersion } //RegionServiceSnapshot RegionServiceSnapshot type RegionServiceSnapshot struct { ServiceID string Service *dbmodel.TenantServices ServiceProbe []*dbmodel.TenantServiceProbe LBMappingPort []*dbmodel.TenantServiceLBMappingPort ServiceEnv []*dbmodel.TenantServiceEnvVar ServiceLabel []*dbmodel.TenantServiceLable ServiceMntRelation []*dbmodel.TenantServiceMountRelation ServiceRelation []*dbmodel.TenantServiceRelation ServiceStatus string ServiceVolume []*dbmodel.TenantServiceVolume ServicePort []*dbmodel.TenantServicesPort Versions []*dbmodel.VersionInfo PluginRelation []*dbmodel.TenantServicePluginRelation PluginConfigs []*dbmodel.TenantPluginVersionDiscoverConfig PluginEnvs []*dbmodel.TenantPluginVersionEnv PluginStreamPorts []*dbmodel.TenantServicesStreamPluginPort } //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 } metaVersion, err := judgeMetadataVersion(metadata) if err != nil { b.Logger.Info(fmt.Sprintf("Failed to judge the version of metadata"), map[string]string{"step": "backup_builder", "status": "failure"}) return err } if metaVersion == OldMetadata { var svcSnapshot []*RegionServiceSnapshot if err := ffjson.Unmarshal(metadata, &svcSnapshot); err != nil { b.Logger.Info(fmt.Sprintf("Failed to unmarshal metadata into RegionServiceSnapshot"), map[string]string{"step": "backup_builder", "status": "failure"}) return err } if err := b.backupServiceInfo(svcSnapshot); err != nil { b.Logger.Info(fmt.Sprintf("Failed to backup metadata service info"), map[string]string{"step": "backup_builder", "status": "failure"}) return err } } else { var appSnapshot AppSnapshot if err := ffjson.Unmarshal(metadata, &appSnapshot); err != nil { b.Logger.Info(fmt.Sprintf("Failed to unmarshal metadata into AppSnapshot"), map[string]string{"step": "backup_builder", "status": "failure"}) return err } if err := b.backupServiceInfo(appSnapshot.Services); err != nil { b.Logger.Info(fmt.Sprintf("Failed to backup metadata service info"), map[string]string{"step": "backup_builder", "status": "failure"}) return err } if err := b.backupPluginInfo(&appSnapshot); err != nil { b.Logger.Info(fmt.Sprintf("Failed to backup metadata plugin info"), map[string]string{"step": "backup_builder", "status": "failure"}) return err } } if strings.HasSuffix(b.SourceDir, "/") { b.SourceDir = b.SourceDir[:len(b.SourceDir)-2] } if err := util.Zip(b.SourceDir, fmt.Sprintf("%s.zip", b.SourceDir)); err != nil { b.Logger.Info(fmt.Sprintf("Compressed backup metadata failed"), map[string]string{"step": "backup_builder", "status": "starting"}) return err } b.BackupSize += util.GetFileSize(fmt.Sprintf("%s.zip", b.SourceDir)) if err := os.RemoveAll(b.SourceDir); err != nil { logrus.Warningf("error removing temporary direcotry: %v", err) } b.SourceDir = fmt.Sprintf("%s.zip", b.SourceDir) if err := b.uploadPkg(); err != nil { return fmt.Errorf("error upload backup package: %v", err) } if err := b.updateBackupStatu("success"); err != nil { return err } return nil } func (b *BackupAPPNew) uploadPkg() error { if b.Mode != "full-online" { return nil } defer func() { if err := os.Remove(b.SourceDir); err != nil { logrus.Warningf("error removing temporary file: %v", err) } }() s3Provider, err := cloudos.Str2S3Provider(b.S3Config.Provider) if err != nil { return err } cfg := &cloudos.Config{ ProviderType: s3Provider, Endpoint: b.S3Config.Endpoint, AccessKey: b.S3Config.AccessKey, SecretKey: b.S3Config.SecretKey, BucketName: b.S3Config.BucketName, } cloudoser, err := cloudos.New(cfg) if err != nil { return fmt.Errorf("error creating cloudoser: %v", err) } _, filename := filepath.Split(b.SourceDir) if err := cloudoser.PutObject(filename, b.SourceDir); err != nil { return fmt.Errorf("object key: %s; filepath: %s; error putting object: %v", filename, b.SourceDir, err) } return nil } // judging whether the metadata structure is old or new, the new version is v5.1.8 and later func judgeMetadataVersion(metadata []byte) (string, error) { var appSnapshot AppSnapshot if err := ffjson.Unmarshal(metadata, &appSnapshot); err == nil { return NewMetadata, nil } var svcSnapshot []*RegionServiceSnapshot if err := ffjson.Unmarshal(metadata, &svcSnapshot); err == nil { return "", err } return OldMetadata, nil } func (b *BackupAPPNew) backupServiceInfo(serviceInfos []*RegionServiceSnapshot) error { for _, app := range serviceInfos { //backup app image or code slug file b.Logger.Info(fmt.Sprintf("Start backup Application(%s) runtime", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "starting"}) if len(app.Versions) > 0 { var backupVersionSize int for _, version := range app.Versions { if backupVersionSize >= maxBackupVersionSize { break } if version.DeliveredType == "slug" && version.FinalStatus == "success" { if ok, _ := b.checkVersionExist(version); !ok { version.FinalStatus = "lost" continue } if err := b.saveSlugPkg(app, version); err != nil { logrus.Errorf("upload app %s version %s slug file error.%s", app.Service.ServiceName, version.BuildVersion, err.Error()) } else { backupVersionSize++ } } if version.DeliveredType == "image" && version.FinalStatus == "success" { if ok, _ := b.checkVersionExist(version); !ok { version.FinalStatus = "lost" continue } if err := b.saveImagePkg(app, version); err != nil { logrus.Errorf("upload app %s version %s image error.%s", app.Service.ServiceName, version.BuildVersion, err.Error()) } else { backupVersionSize++ } } } if backupVersionSize == 0 { b.Logger.Error(fmt.Sprintf("Application(%s) Backup build version failure.", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "success"}) return fmt.Errorf("Application(%s) Backup build version failure", app.Service.ServiceAlias) } logrus.Infof("backup app %s %d version", app.Service.ServiceName, backupVersionSize) b.Logger.Info(fmt.Sprintf("Complete backup application (%s) runtime %d version", app.Service.ServiceAlias, backupVersionSize), map[string]string{"step": "backup_builder", "status": "success"}) } b.Logger.Info(fmt.Sprintf("Start backup application(%s) persistent data", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "starting"}) //backup app data,The overall data of the direct backup service if len(app.ServiceVolume) > 0 { dstDir := fmt.Sprintf("%s/data_%s/%s.zip", b.SourceDir, "all", "data") _, sharepath := GetVolumeDir() serviceVolumeData := path.Join(sharepath, "tenant", app.Service.TenantID, "service", app.Service.ServiceID) if !util.DirIsEmpty(serviceVolumeData) { if err := util.Zip(serviceVolumeData, dstDir); err != nil { logrus.Errorf("backup service(%s) volume data error.%s", app.ServiceID, err.Error()) return err } } } for _, volume := range app.ServiceVolume { dstDir := fmt.Sprintf("%s/data_%s/%s.zip", b.SourceDir, app.ServiceID, strings.Replace(volume.VolumeName, "/", "", -1)) hostPath := volume.HostPath if hostPath != "" && !util.DirIsEmpty(hostPath) { if err := util.Zip(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("Complete backup application(%s) persistent data", app.Service.ServiceAlias), map[string]string{"step": "backup_builder", "status": "success"}) } return nil } func (b *BackupAPPNew) backupPluginInfo(appSnapshot *AppSnapshot) error { b.Logger.Info(fmt.Sprintf("Start backup plugin"), map[string]string{"step": "backup_builder", "status": "starting"}) for _, pv := range appSnapshot.PluginBuildVersions { dstDir := fmt.Sprintf("%s/plugin_%s/image_%s.tar", b.SourceDir, pv.PluginID, pv.DeployVersion) util.CheckAndCreateDir(filepath.Dir(dstDir)) if _, err := sources.ImagePull(b.DockerClient, pv.BuildLocalImage, "", "", b.Logger, 20); err != nil { b.Logger.Error(fmt.Sprintf("plugin image: %s; failed to pull image", pv.BuildLocalImage), map[string]string{"step": "backup_builder", "status": "failure"}) logrus.Errorf("plugin image: %s; failed to pull image: %v", pv.BuildLocalImage, err) return err } if err := sources.ImageSave(b.DockerClient, pv.BuildLocalImage, 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("plugin image: %s; failed to save image: %v", pv.BuildLocalImage, err) return err } } return nil } func (b *BackupAPPNew) checkVersionExist(version *dbmodel.VersionInfo) (bool, error) { if version.DeliveredType == "image" { imageInfo := sources.ImageNameHandle(version.DeliveredPath) reg, err := registry.NewInsecure(imageInfo.Host, "", "") if err != nil { logrus.Errorf("new registry client error %s", err.Error()) return false, err } _, err = reg.Manifest(imageInfo.Name, imageInfo.Tag) if err != nil { logrus.Errorf("get image %s manifest info failure, it could be not exist", version.DeliveredPath) return false, err } return true, nil } if version.DeliveredType == "slug" { islugfile, err := os.Stat(version.DeliveredPath) if os.IsNotExist(err) { return false, nil } else if err != nil { return false, err } if islugfile.IsDir() { return false, nil } return true, nil } return false, fmt.Errorf("delivered type is invalid") } // saveSlugPkg saves slug package on disk. func (b *BackupAPPNew) saveSlugPkg(app *RegionServiceSnapshot, version *dbmodel.VersionInfo) error { dstDir := fmt.Sprintf("%s/app_%s/slug_%s.tgz", b.SourceDir, app.ServiceID, version.BuildVersion) util.CheckAndCreateDir(filepath.Dir(dstDir)) 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 } // saveSlugPkg saves image package on disk. func (b *BackupAPPNew) saveImagePkg(app *RegionServiceSnapshot, version *dbmodel.VersionInfo) error { dstDir := fmt.Sprintf("%s/app_%s/image_%s.tar", b.SourceDir, app.ServiceID, version.BuildVersion) util.CheckAndCreateDir(filepath.Dir(dstDir)) if _, err := sources.ImagePull(b.DockerClient, version.DeliveredPath, "", "", b.Logger, 20); err != nil { b.Logger.Error(util.Translation("error pulling image"), map[string]string{"step": "backup_builder", "status": "failure"}) logrus.Errorf(fmt.Sprintf("image: %s; error pulling image: %v", version.DeliveredPath, err), version.DeliveredPath, err.Error()) } if err := sources.ImageSave(b.DockerClient, version.DeliveredPath, 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(%s) to local dir error when backup app, %s", version.DeliveredPath, 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"}) b.updateBackupStatu("failed") } } func (b *BackupAPPNew) updateBackupStatu(status string) error { backupstatus, err := db.GetManager().AppBackupDao().GetAppBackup(b.BackupID) if err != nil { logrus.Errorf("update backup group app history failure %s", err) return err } backupstatus.Status = status backupstatus.SourceDir = b.SourceDir backupstatus.SourceType = b.SourceType backupstatus.BuckupSize = b.BackupSize return db.GetManager().AppBackupDao().UpdateModel(backupstatus) } //GetVolumeDir get volume path prifix func GetVolumeDir() (string, string) { localPath := os.Getenv("LOCAL_DATA_PATH") sharePath := os.Getenv("SHARE_DATA_PATH") if localPath == "" { localPath = "/grlocaldata" } if sharePath == "" { sharePath = "/grdata" } return localPath, sharePath }