blob: ceda54a37d7cfc6f00ce335232d4538eb9ff19df [file] [log] [blame]
Scott Baker6cf525a2019-05-09 12:25:08 -07001/*
2 * Portions copyright 2019-present Open Networking Foundation
3 * Original copyright 2019-present Ciena Corporation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package commands
18
19import (
Scott Bakerc328cf12019-05-28 16:03:12 -070020 "context"
Scott Baker6cf525a2019-05-09 12:25:08 -070021 flags "github.com/jessevdk/go-flags"
Scott Baker20481aa2019-06-20 11:00:54 -070022 corderrors "github.com/opencord/cordctl/error"
Scott Baker6cf525a2019-05-09 12:25:08 -070023 "time"
24)
25
26const (
27 DEFAULT_BACKUP_FORMAT = "table{{ .Status }}\t{{ .Checksum }}\t{{ .Chunks }}\t{{ .Bytes }}"
28)
29
30type BackupOutput struct {
31 Status string `json:"status"`
32 Checksum string `json:"checksum"`
33 Chunks int `json:"chunks"`
34 Bytes int `json:"bytes"`
35}
36
37type BackupCreate struct {
38 OutputOptions
Scott Bakerf53bf152019-05-29 17:50:37 -070039 ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Chunk size for streaming transfer"`
Scott Baker6cf525a2019-05-09 12:25:08 -070040 Args struct {
41 LocalFileName string
42 } `positional-args:"yes" required:"yes"`
43}
44
45type BackupRestore struct {
46 OutputOptions
Scott Bakerf53bf152019-05-29 17:50:37 -070047 ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Chunk size for streaming transfer"`
48 Args struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070049 LocalFileName string
50 } `positional-args:"yes" required:"yes"`
Scott Bakerf53bf152019-05-29 17:50:37 -070051 CreateURIFunc func() (string, string) // allow override of CreateURIFunc for easy unit testing
Scott Baker6cf525a2019-05-09 12:25:08 -070052}
53
54type BackupOpts struct {
55 Create BackupCreate `command:"create"`
56 Restore BackupRestore `command:"restore"`
57}
58
59var backupOpts = BackupOpts{}
60
61func RegisterBackupCommands(parser *flags.Parser) {
62 parser.AddCommand("backup", "backup management commands", "Commands to create backups and restore backups", &backupOpts)
63}
64
65func (options *BackupCreate) Execute(args []string) error {
Scott Baker867aa302019-06-19 13:18:45 -070066 conn, descriptor, err := InitClient(INIT_DEFAULT)
Scott Baker6cf525a2019-05-09 12:25:08 -070067 if err != nil {
68 return err
69 }
70 defer conn.Close()
71
Scott Bakerc328cf12019-05-28 16:03:12 -070072 ctx := context.Background() // TODO: Implement a sync timeout
73
Scott Baker6cf525a2019-05-09 12:25:08 -070074 // We might close and reopen the connection befor we do the DownloadFile,
75 // so make sure we've downloaded the service descriptor.
76 _, err = descriptor.FindSymbol("xos.filetransfer")
77 if err != nil {
78 return err
79 }
80
81 local_name := options.Args.LocalFileName
82
83 // STEP 1: Create backup operation
Scott Baker72efd752019-05-15 13:12:20 -070084
Scott Baker6cf525a2019-05-09 12:25:08 -070085 backupop := make(map[string]interface{})
86 backupop["operation"] = "create"
87 err = CreateModel(conn, descriptor, "BackupOperation", backupop)
88 if err != nil {
89 return err
90 }
91 conditional_printf(!options.Quiet, "Created backup-create operation id=%d uuid=%s\n", backupop["id"], backupop["uuid"])
92 conditional_printf(!options.Quiet, "Waiting for sync ")
93
94 // STEP 2: Wait for the operation to complete
Scott Baker72efd752019-05-15 13:12:20 -070095
Scott Baker6cf525a2019-05-09 12:25:08 -070096 flags := GM_UNTIL_ENACTED | GM_UNTIL_FOUND | Ternary_uint32(options.Quiet, GM_QUIET, 0)
Scott Bakerc328cf12019-05-28 16:03:12 -070097 conn, completed_backupop, err := GetModelWithRetry(ctx, conn, descriptor, "BackupOperation", backupop["id"].(int32), flags)
Scott Baker6cf525a2019-05-09 12:25:08 -070098 if err != nil {
99 return err
100 }
101
102 defer conn.Close()
103
104 status := completed_backupop.GetFieldByName("status").(string)
105 conditional_printf(!options.Quiet, "\nStatus: %s\n", status)
106
107 // we've failed. leave.
108 if status != "created" {
Scott Baker20481aa2019-06-20 11:00:54 -0700109 return corderrors.NewInternalError("BackupOp status is %s", status)
Scott Baker6cf525a2019-05-09 12:25:08 -0700110 }
111
112 // STEP 3: Retrieve URI
113 backupfile_id := completed_backupop.GetFieldByName("file_id").(int32)
114 if backupfile_id == 0 {
Scott Baker20481aa2019-06-20 11:00:54 -0700115 return corderrors.NewInternalError("BackupOp.file_id is not set")
Scott Baker6cf525a2019-05-09 12:25:08 -0700116 }
117
Scott Bakerc328cf12019-05-28 16:03:12 -0700118 completed_backupfile, err := GetModel(ctx, conn, descriptor, "BackupFile", backupfile_id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700119 if err != nil {
120 return err
121 }
122
123 uri := completed_backupfile.GetFieldByName("uri").(string)
124 conditional_printf(!options.Quiet, "URI %s\n", uri)
125
126 // STEP 4: Download the file
127
128 conditional_printf(!options.Quiet, "Downloading %s\n", local_name)
129
130 h, err := DownloadFile(conn, descriptor, uri, local_name)
131 if err != nil {
132 return err
133 }
134
Scott Baker72efd752019-05-15 13:12:20 -0700135 // STEP 5: Verify checksum
136
137 if completed_backupfile.GetFieldByName("checksum").(string) != h.GetChecksum() {
Scott Baker20481aa2019-06-20 11:00:54 -0700138 return corderrors.WithStackTrace(&corderrors.ChecksumMismatchError{
139 Actual: h.GetChecksum(),
140 Expected: completed_backupfile.GetFieldByName("checksum").(string)})
Scott Baker72efd752019-05-15 13:12:20 -0700141 }
142
143 // STEP 6: Show results
144
Scott Baker6cf525a2019-05-09 12:25:08 -0700145 data := make([]BackupOutput, 1)
146 data[0].Chunks = h.chunks
147 data[0].Bytes = h.bytes
148 data[0].Status = h.status
Scott Baker72efd752019-05-15 13:12:20 -0700149 data[0].Checksum = h.GetChecksum()
Scott Baker6cf525a2019-05-09 12:25:08 -0700150
Scott Baker5281d002019-05-16 10:45:26 -0700151 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_BACKUP_FORMAT, "{{.Status}}", data)
Scott Baker6cf525a2019-05-09 12:25:08 -0700152
153 return nil
154}
155
Scott Bakerf53bf152019-05-29 17:50:37 -0700156// Create a file:/// URI to use for storing the file in the core
157func CreateDynamicURI() (string, string) {
158 remote_name := "cordctl-restore-" + time.Now().Format("20060102T150405Z")
159 uri := "file:///var/run/xos/backup/local/" + remote_name
160 return remote_name, uri
161}
162
Scott Baker6cf525a2019-05-09 12:25:08 -0700163func (options *BackupRestore) Execute(args []string) error {
Scott Baker867aa302019-06-19 13:18:45 -0700164 conn, descriptor, err := InitClient(INIT_DEFAULT)
Scott Baker6cf525a2019-05-09 12:25:08 -0700165 if err != nil {
166 return err
167 }
168 defer conn.Close()
169
Scott Bakerc328cf12019-05-28 16:03:12 -0700170 ctx := context.Background() // TODO: Implement a sync timeout
171
Scott Baker6cf525a2019-05-09 12:25:08 -0700172 local_name := options.Args.LocalFileName
Scott Bakerf53bf152019-05-29 17:50:37 -0700173
174 var remote_name, uri string
175 if options.CreateURIFunc != nil {
176 remote_name, uri = options.CreateURIFunc()
177 } else {
178 remote_name, uri = CreateDynamicURI()
179 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700180
181 // STEP 1: Upload the file
182
Scott Bakerf53bf152019-05-29 17:50:37 -0700183 h, upload_result, err := UploadFile(conn, descriptor, local_name, uri, options.ChunkSize)
Scott Baker6cf525a2019-05-09 12:25:08 -0700184 if err != nil {
185 return err
186 }
187
188 upload_status := GetEnumValue(upload_result, "status")
189 if upload_status != "SUCCESS" {
Scott Baker20481aa2019-06-20 11:00:54 -0700190 return corderrors.NewInternalError("Upload status was %s", upload_status)
Scott Baker6cf525a2019-05-09 12:25:08 -0700191 }
192
Scott Baker72efd752019-05-15 13:12:20 -0700193 // STEP 2: Verify checksum
194
195 if upload_result.GetFieldByName("checksum").(string) != h.GetChecksum() {
Scott Baker20481aa2019-06-20 11:00:54 -0700196 return corderrors.WithStackTrace(&corderrors.ChecksumMismatchError{
197 Expected: h.GetChecksum(),
198 Actual: upload_result.GetFieldByName("checksum").(string)})
Scott Baker72efd752019-05-15 13:12:20 -0700199 }
200
Scott Baker6cf525a2019-05-09 12:25:08 -0700201 // STEP 2: Create a BackupFile object
Scott Baker72efd752019-05-15 13:12:20 -0700202
Scott Baker6cf525a2019-05-09 12:25:08 -0700203 backupfile := make(map[string]interface{})
204 backupfile["name"] = remote_name
205 backupfile["uri"] = uri
Scott Baker72efd752019-05-15 13:12:20 -0700206 backupfile["checksum"] = h.GetChecksum()
Scott Baker6cf525a2019-05-09 12:25:08 -0700207 err = CreateModel(conn, descriptor, "BackupFile", backupfile)
208 if err != nil {
209 return err
210 }
211 conditional_printf(!options.Quiet, "Created backup file %d\n", backupfile["id"])
212
213 // STEP 3: Create a BackupOperation object
Scott Baker72efd752019-05-15 13:12:20 -0700214
Scott Baker6cf525a2019-05-09 12:25:08 -0700215 backupop := make(map[string]interface{})
216 backupop["operation"] = "restore"
217 backupop["file_id"] = backupfile["id"]
218 err = CreateModel(conn, descriptor, "BackupOperation", backupop)
219 if err != nil {
220 return err
221 }
222 conditional_printf(!options.Quiet, "Created backup-restore operation id=%d uuid=%s\n", backupop["id"], backupop["uuid"])
223
224 conditional_printf(!options.Quiet, "Waiting for completion ")
225
226 // STEP 4: Wait for completion
Scott Baker72efd752019-05-15 13:12:20 -0700227
Scott Baker6cf525a2019-05-09 12:25:08 -0700228 flags := GM_UNTIL_ENACTED | GM_UNTIL_FOUND | GM_UNTIL_STATUS | Ternary_uint32(options.Quiet, GM_QUIET, 0)
Scott Baker5201c0b2019-05-15 15:35:56 -0700229 queries := map[string]string{"uuid": backupop["uuid"].(string)}
Scott Bakerc328cf12019-05-28 16:03:12 -0700230 conn, completed_backupop, err := FindModelWithRetry(ctx, conn, descriptor, "BackupOperation", queries, flags)
Scott Baker6cf525a2019-05-09 12:25:08 -0700231 if err != nil {
232 return err
233 }
234
235 defer conn.Close()
236
237 conditional_printf(!options.Quiet, "\n")
238
239 // STEP 5: Show results
Scott Baker72efd752019-05-15 13:12:20 -0700240
Scott Baker6cf525a2019-05-09 12:25:08 -0700241 data := make([]BackupOutput, 1)
242 data[0].Checksum = upload_result.GetFieldByName("checksum").(string)
243 data[0].Chunks = int(upload_result.GetFieldByName("chunks_received").(int32))
244 data[0].Bytes = int(upload_result.GetFieldByName("bytes_received").(int32))
245
246 if completed_backupop.GetFieldByName("status") == "restored" {
247 data[0].Status = "SUCCESS"
248 } else {
249 data[0].Status = "FAILURE"
250 }
251
Scott Baker5281d002019-05-16 10:45:26 -0700252 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_BACKUP_FORMAT, "{{.Status}}", data)
Scott Baker6cf525a2019-05-09 12:25:08 -0700253
254 return nil
255}