blob: fc5d509a4ca13ee8551132ef243849e9c7def991 [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 "errors"
22 "fmt"
23 flags "github.com/jessevdk/go-flags"
Scott Baker6cf525a2019-05-09 12:25:08 -070024 "time"
25)
26
27const (
28 DEFAULT_BACKUP_FORMAT = "table{{ .Status }}\t{{ .Checksum }}\t{{ .Chunks }}\t{{ .Bytes }}"
29)
30
31type BackupOutput struct {
32 Status string `json:"status"`
33 Checksum string `json:"checksum"`
34 Chunks int `json:"chunks"`
35 Bytes int `json:"bytes"`
36}
37
38type BackupCreate struct {
39 OutputOptions
Scott Bakerf53bf152019-05-29 17:50:37 -070040 ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Chunk size for streaming transfer"`
Scott Baker6cf525a2019-05-09 12:25:08 -070041 Args struct {
42 LocalFileName string
43 } `positional-args:"yes" required:"yes"`
44}
45
46type BackupRestore struct {
47 OutputOptions
Scott Bakerf53bf152019-05-29 17:50:37 -070048 ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Chunk size for streaming transfer"`
49 Args struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070050 LocalFileName string
51 } `positional-args:"yes" required:"yes"`
Scott Bakerf53bf152019-05-29 17:50:37 -070052 CreateURIFunc func() (string, string) // allow override of CreateURIFunc for easy unit testing
Scott Baker6cf525a2019-05-09 12:25:08 -070053}
54
55type BackupOpts struct {
56 Create BackupCreate `command:"create"`
57 Restore BackupRestore `command:"restore"`
58}
59
60var backupOpts = BackupOpts{}
61
62func RegisterBackupCommands(parser *flags.Parser) {
63 parser.AddCommand("backup", "backup management commands", "Commands to create backups and restore backups", &backupOpts)
64}
65
66func (options *BackupCreate) Execute(args []string) error {
67 conn, descriptor, err := InitReflectionClient()
68 if err != nil {
69 return err
70 }
71 defer conn.Close()
72
Scott Bakerc328cf12019-05-28 16:03:12 -070073 ctx := context.Background() // TODO: Implement a sync timeout
74
Scott Baker6cf525a2019-05-09 12:25:08 -070075 // We might close and reopen the connection befor we do the DownloadFile,
76 // so make sure we've downloaded the service descriptor.
77 _, err = descriptor.FindSymbol("xos.filetransfer")
78 if err != nil {
79 return err
80 }
81
82 local_name := options.Args.LocalFileName
83
84 // STEP 1: Create backup operation
Scott Baker72efd752019-05-15 13:12:20 -070085
Scott Baker6cf525a2019-05-09 12:25:08 -070086 backupop := make(map[string]interface{})
87 backupop["operation"] = "create"
88 err = CreateModel(conn, descriptor, "BackupOperation", backupop)
89 if err != nil {
90 return err
91 }
92 conditional_printf(!options.Quiet, "Created backup-create operation id=%d uuid=%s\n", backupop["id"], backupop["uuid"])
93 conditional_printf(!options.Quiet, "Waiting for sync ")
94
95 // STEP 2: Wait for the operation to complete
Scott Baker72efd752019-05-15 13:12:20 -070096
Scott Baker6cf525a2019-05-09 12:25:08 -070097 flags := GM_UNTIL_ENACTED | GM_UNTIL_FOUND | Ternary_uint32(options.Quiet, GM_QUIET, 0)
Scott Bakerc328cf12019-05-28 16:03:12 -070098 conn, completed_backupop, err := GetModelWithRetry(ctx, conn, descriptor, "BackupOperation", backupop["id"].(int32), flags)
Scott Baker6cf525a2019-05-09 12:25:08 -070099 if err != nil {
100 return err
101 }
102
103 defer conn.Close()
104
105 status := completed_backupop.GetFieldByName("status").(string)
106 conditional_printf(!options.Quiet, "\nStatus: %s\n", status)
107
108 // we've failed. leave.
109 if status != "created" {
110 return errors.New("BackupOp status is " + status)
111 }
112
113 // STEP 3: Retrieve URI
114 backupfile_id := completed_backupop.GetFieldByName("file_id").(int32)
115 if backupfile_id == 0 {
116 return errors.New("BackupOp.file_id is not set")
117 }
118
Scott Bakerc328cf12019-05-28 16:03:12 -0700119 completed_backupfile, err := GetModel(ctx, conn, descriptor, "BackupFile", backupfile_id)
Scott Baker6cf525a2019-05-09 12:25:08 -0700120 if err != nil {
121 return err
122 }
123
124 uri := completed_backupfile.GetFieldByName("uri").(string)
125 conditional_printf(!options.Quiet, "URI %s\n", uri)
126
127 // STEP 4: Download the file
128
129 conditional_printf(!options.Quiet, "Downloading %s\n", local_name)
130
131 h, err := DownloadFile(conn, descriptor, uri, local_name)
132 if err != nil {
133 return err
134 }
135
Scott Baker72efd752019-05-15 13:12:20 -0700136 // STEP 5: Verify checksum
137
138 if completed_backupfile.GetFieldByName("checksum").(string) != h.GetChecksum() {
139 return fmt.Errorf("Checksum mismatch, received=%s, expected=%s",
140 h.GetChecksum(),
141 completed_backupfile.GetFieldByName("checksum").(string))
142 }
143
144 // STEP 6: Show results
145
Scott Baker6cf525a2019-05-09 12:25:08 -0700146 data := make([]BackupOutput, 1)
147 data[0].Chunks = h.chunks
148 data[0].Bytes = h.bytes
149 data[0].Status = h.status
Scott Baker72efd752019-05-15 13:12:20 -0700150 data[0].Checksum = h.GetChecksum()
Scott Baker6cf525a2019-05-09 12:25:08 -0700151
Scott Baker5281d002019-05-16 10:45:26 -0700152 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_BACKUP_FORMAT, "{{.Status}}", data)
Scott Baker6cf525a2019-05-09 12:25:08 -0700153
154 return nil
155}
156
Scott Bakerf53bf152019-05-29 17:50:37 -0700157// Create a file:/// URI to use for storing the file in the core
158func CreateDynamicURI() (string, string) {
159 remote_name := "cordctl-restore-" + time.Now().Format("20060102T150405Z")
160 uri := "file:///var/run/xos/backup/local/" + remote_name
161 return remote_name, uri
162}
163
Scott Baker6cf525a2019-05-09 12:25:08 -0700164func (options *BackupRestore) Execute(args []string) error {
165 conn, descriptor, err := InitReflectionClient()
166 if err != nil {
167 return err
168 }
169 defer conn.Close()
170
Scott Bakerc328cf12019-05-28 16:03:12 -0700171 ctx := context.Background() // TODO: Implement a sync timeout
172
Scott Baker6cf525a2019-05-09 12:25:08 -0700173 local_name := options.Args.LocalFileName
Scott Bakerf53bf152019-05-29 17:50:37 -0700174
175 var remote_name, uri string
176 if options.CreateURIFunc != nil {
177 remote_name, uri = options.CreateURIFunc()
178 } else {
179 remote_name, uri = CreateDynamicURI()
180 }
Scott Baker6cf525a2019-05-09 12:25:08 -0700181
182 // STEP 1: Upload the file
183
Scott Bakerf53bf152019-05-29 17:50:37 -0700184 h, upload_result, err := UploadFile(conn, descriptor, local_name, uri, options.ChunkSize)
Scott Baker6cf525a2019-05-09 12:25:08 -0700185 if err != nil {
186 return err
187 }
188
189 upload_status := GetEnumValue(upload_result, "status")
190 if upload_status != "SUCCESS" {
191 return errors.New("Upload status was " + upload_status)
192 }
193
Scott Baker72efd752019-05-15 13:12:20 -0700194 // STEP 2: Verify checksum
195
196 if upload_result.GetFieldByName("checksum").(string) != h.GetChecksum() {
197 return fmt.Errorf("Checksum mismatch, expected=%s, received=%s",
198 h.GetChecksum(),
199 upload_result.GetFieldByName("checksum").(string))
200 }
201
Scott Baker6cf525a2019-05-09 12:25:08 -0700202 // STEP 2: Create a BackupFile object
Scott Baker72efd752019-05-15 13:12:20 -0700203
Scott Baker6cf525a2019-05-09 12:25:08 -0700204 backupfile := make(map[string]interface{})
205 backupfile["name"] = remote_name
206 backupfile["uri"] = uri
Scott Baker72efd752019-05-15 13:12:20 -0700207 backupfile["checksum"] = h.GetChecksum()
Scott Baker6cf525a2019-05-09 12:25:08 -0700208 err = CreateModel(conn, descriptor, "BackupFile", backupfile)
209 if err != nil {
210 return err
211 }
212 conditional_printf(!options.Quiet, "Created backup file %d\n", backupfile["id"])
213
214 // STEP 3: Create a BackupOperation object
Scott Baker72efd752019-05-15 13:12:20 -0700215
Scott Baker6cf525a2019-05-09 12:25:08 -0700216 backupop := make(map[string]interface{})
217 backupop["operation"] = "restore"
218 backupop["file_id"] = backupfile["id"]
219 err = CreateModel(conn, descriptor, "BackupOperation", backupop)
220 if err != nil {
221 return err
222 }
223 conditional_printf(!options.Quiet, "Created backup-restore operation id=%d uuid=%s\n", backupop["id"], backupop["uuid"])
224
225 conditional_printf(!options.Quiet, "Waiting for completion ")
226
227 // STEP 4: Wait for completion
Scott Baker72efd752019-05-15 13:12:20 -0700228
Scott Baker6cf525a2019-05-09 12:25:08 -0700229 flags := GM_UNTIL_ENACTED | GM_UNTIL_FOUND | GM_UNTIL_STATUS | Ternary_uint32(options.Quiet, GM_QUIET, 0)
Scott Baker5201c0b2019-05-15 15:35:56 -0700230 queries := map[string]string{"uuid": backupop["uuid"].(string)}
Scott Bakerc328cf12019-05-28 16:03:12 -0700231 conn, completed_backupop, err := FindModelWithRetry(ctx, conn, descriptor, "BackupOperation", queries, flags)
Scott Baker6cf525a2019-05-09 12:25:08 -0700232 if err != nil {
233 return err
234 }
235
236 defer conn.Close()
237
238 conditional_printf(!options.Quiet, "\n")
239
240 // STEP 5: Show results
Scott Baker72efd752019-05-15 13:12:20 -0700241
Scott Baker6cf525a2019-05-09 12:25:08 -0700242 data := make([]BackupOutput, 1)
243 data[0].Checksum = upload_result.GetFieldByName("checksum").(string)
244 data[0].Chunks = int(upload_result.GetFieldByName("chunks_received").(int32))
245 data[0].Bytes = int(upload_result.GetFieldByName("bytes_received").(int32))
246
247 if completed_backupop.GetFieldByName("status") == "restored" {
248 data[0].Status = "SUCCESS"
249 } else {
250 data[0].Status = "FAILURE"
251 }
252
Scott Baker5281d002019-05-16 10:45:26 -0700253 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_BACKUP_FORMAT, "{{.Status}}", data)
Scott Baker6cf525a2019-05-09 12:25:08 -0700254
255 return nil
256}