1264cf0abSJim Walker/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2264cf0abSJim Walker/*
3264cf0abSJim Walker *     Copyright 2018 Couchbase, Inc
4264cf0abSJim Walker *
5264cf0abSJim Walker *   Licensed under the Apache License, Version 2.0 (the "License");
6264cf0abSJim Walker *   you may not use this file except in compliance with the License.
7264cf0abSJim Walker *   You may obtain a copy of the License at
8264cf0abSJim Walker *
9264cf0abSJim Walker *       http://www.apache.org/licenses/LICENSE-2.0
10264cf0abSJim Walker *
11264cf0abSJim Walker *   Unless required by applicable law or agreed to in writing, software
12264cf0abSJim Walker *   distributed under the License is distributed on an "AS IS" BASIS,
13264cf0abSJim Walker *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14264cf0abSJim Walker *   See the License for the specific language governing permissions and
15264cf0abSJim Walker *   limitations under the License.
16264cf0abSJim Walker */
17264cf0abSJim Walker
18264cf0abSJim Walker#include "input_couchfile.h"
19264cf0abSJim Walker#include "output_couchfile.h"
20264cf0abSJim Walker
21264cf0abSJim Walker#include <getopt.h>
22264cf0abSJim Walker#include <cstdlib>
23264cf0abSJim Walker#include <iostream>
24264cf0abSJim Walker#include <memory>
25264cf0abSJim Walker#include <vector>
26264cf0abSJim Walker
27264cf0abSJim Walker/**
28264cf0abSJim Walker * couchfile_upgrade is a tool for making couchstore files collection-aware.
29264cf0abSJim Walker * This is essentially a set of steps which result in a new couchstore file
30264cf0abSJim Walker * that has each document assigned to the DefaultCollection.
31264cf0abSJim Walker */
32264cf0abSJim Walker
33264cf0abSJim Walkerstruct ProgramOptions {
34264cf0abSJim Walker    OptionsSet options;
35264cf0abSJim Walker    const char* inputFilename;
36264cf0abSJim Walker    const char* outputFilename;
37fa794508SJim Walker    size_t outputBufferMaxSize = (1024 * 1024) * 500;
38264cf0abSJim Walker};
39264cf0abSJim Walker
40264cf0abSJim Walkerstatic bool runUpgrade(const ProgramOptions& options,
41f9266326SJim Walker                       Collections::InputCouchFile& input) {
42264cf0abSJim Walker    using PreflightStatus = Collections::InputCouchFile::PreflightStatus;
43264cf0abSJim Walker    switch (input.preflightChecks(std::cerr)) {
44264cf0abSJim Walker    case PreflightStatus::ReadyForUpgrade:
45264cf0abSJim Walker        break;
46f9266326SJim Walker    case PreflightStatus::InputFileCannotBeProcessed:
47264cf0abSJim Walker    case PreflightStatus::UpgradePartial:
48264cf0abSJim Walker    case PreflightStatus::UpgradeCompleteAndPartial: {
49264cf0abSJim Walker        std::cerr << "Pre-upgrade checks have failed\n";
50264cf0abSJim Walker        return false;
51264cf0abSJim Walker    }
52264cf0abSJim Walker    case PreflightStatus::UpgradeComplete: {
53264cf0abSJim Walker        return options.options.test(Options::Tolerate);
54264cf0abSJim Walker    }
55264cf0abSJim Walker    }
56264cf0abSJim Walker
57f9266326SJim Walker    // Open the output file now that the input file is ok for processing
58fa794508SJim Walker    Collections::OutputCouchFile output(options.options,
59fa794508SJim Walker                                        options.outputFilename,
60fa794508SJim Walker                                        CollectionID::Default,
61fa794508SJim Walker                                        options.outputBufferMaxSize);
62f9266326SJim Walker
63264cf0abSJim Walker    // Perform the upgrade steps
64264cf0abSJim Walker    // 1. Write out a new file tagged in a way we can determine upgrade has
65264cf0abSJim Walker    //    started but not finished
66264cf0abSJim Walker    output.writeUpgradeBegin(input);
67264cf0abSJim Walker    output.commit();
68264cf0abSJim Walker
69264cf0abSJim Walker    // 2. Now run the upgrade, docs are copied from in to out and moved to
70264cf0abSJim Walker    //    the default collection
71264cf0abSJim Walker    input.upgrade(output);
72264cf0abSJim Walker    output.commit();
73264cf0abSJim Walker
74264cf0abSJim Walker    // 3. Write out to the new file that the upgrade is done, KV can now warmup
75264cf0abSJim Walker    //    from this file.
76264cf0abSJim Walker    output.writeUpgradeComplete(input);
77264cf0abSJim Walker    output.commit();
78264cf0abSJim Walker    return true;
79264cf0abSJim Walker}
80264cf0abSJim Walker
81264cf0abSJim Walkerstatic void runStatus(Collections::InputCouchFile& input) {
82264cf0abSJim Walker    if (input.preflightChecks(std::cout) ==
83264cf0abSJim Walker        Collections::InputCouchFile::PreflightStatus::ReadyForUpgrade) {
84264cf0abSJim Walker        std::cout << "filename: " << input.getFilename()
85264cf0abSJim Walker                  << " is ready for upgrade\n";
86264cf0abSJim Walker    }
87264cf0abSJim Walker}
88264cf0abSJim Walker
89264cf0abSJim Walkerstatic void usage() {
90264cf0abSJim Walker    std::cout <<
91264cf0abSJim Walker            R"(Usage:
92264cf0abSJim Walker    -v or --verbose        Optional: Run with verbose output to stdout.
93264cf0abSJim Walker    -s or --status         Optional: Print upgrade status of input file.
94264cf0abSJim Walker    -t or --tolerate       Optional: Tolerate upgraded files - exit 0 if file is already marked as upgraded.
95264cf0abSJim Walker    -i or --input <name>   Required: Input filename.
96fa794508SJim Walker    -o or --output <name>  Required (only if not -s): Output filename to be created.
97fa794508SJim Walker    -b or --buffer size    Optional: Specify the amount of memory (bytes) we can use for buffering documents (default 524288000))"
98264cf0abSJim Walker              << std::endl;
99264cf0abSJim Walker}
100264cf0abSJim Walker
101264cf0abSJim Walkerstatic ProgramOptions parseArguments(int argc, char** argv) {
102264cf0abSJim Walker    int cmd = 0;
103264cf0abSJim Walker    ProgramOptions pOptions{};
104264cf0abSJim Walker
105264cf0abSJim Walker    struct option long_options[] = {{"tolerate", no_argument, nullptr, 't'},
106264cf0abSJim Walker                                    {"status", no_argument, nullptr, 's'},
107264cf0abSJim Walker                                    {"verbose", no_argument, nullptr, 'v'},
108264cf0abSJim Walker                                    {"input", required_argument, nullptr, 'i'},
109264cf0abSJim Walker                                    {"output", required_argument, nullptr, 'o'},
110fa794508SJim Walker                                    {"buffer", optional_argument, nullptr, 'b'},
111264cf0abSJim Walker                                    {nullptr, 0, nullptr, 0}};
112264cf0abSJim Walker
113fa794508SJim Walker    while ((cmd = getopt_long(
114fa794508SJim Walker                    argc, argv, "tsvi:o:b:", long_options, nullptr)) != -1) {
115264cf0abSJim Walker        switch (cmd) {
116264cf0abSJim Walker        case 'v': {
117264cf0abSJim Walker            pOptions.options.set(Options::Verbose);
118264cf0abSJim Walker            std::cout << "Enabling Verbose\n";
119264cf0abSJim Walker            break;
120264cf0abSJim Walker        }
121264cf0abSJim Walker        case 's': {
122264cf0abSJim Walker            pOptions.options.set(Options::Status);
123264cf0abSJim Walker            std::cout << "Status\n";
124264cf0abSJim Walker            break;
125264cf0abSJim Walker        }
126264cf0abSJim Walker        case 't': {
127264cf0abSJim Walker            pOptions.options.set(Options::Tolerate);
128264cf0abSJim Walker            std::cout << "exit(0) for already upgraded files\n";
129264cf0abSJim Walker            break;
130264cf0abSJim Walker        }
131264cf0abSJim Walker        case 'i': {
132264cf0abSJim Walker            pOptions.inputFilename = optarg;
133264cf0abSJim Walker            std::cout << "Input:" << optarg << "\n";
134264cf0abSJim Walker            break;
135264cf0abSJim Walker        }
136264cf0abSJim Walker        case 'o': {
137264cf0abSJim Walker            pOptions.outputFilename = optarg;
138264cf0abSJim Walker            std::cout << "Output:" << optarg << "\n";
139264cf0abSJim Walker            break;
140264cf0abSJim Walker        }
141fa794508SJim Walker        case 'b': {
142fa794508SJim Walker            pOptions.outputBufferMaxSize = std::stoll(optarg);
143fa794508SJim Walker            std::cout << "Buffer size:" << pOptions.outputBufferMaxSize << "\n";
144fa794508SJim Walker            break;
145fa794508SJim Walker        }
146264cf0abSJim Walker        case ':':
147264cf0abSJim Walker        case '?': {
148264cf0abSJim Walker            usage();
149264cf0abSJim Walker            throw std::invalid_argument("Invalid Argument");
150264cf0abSJim Walker            break;
151264cf0abSJim Walker        }
152264cf0abSJim Walker        }
153264cf0abSJim Walker    }
154264cf0abSJim Walker
155264cf0abSJim Walker    if (!pOptions.inputFilename) {
156264cf0abSJim Walker        usage();
157264cf0abSJim Walker        throw std::invalid_argument("Missing -i");
158264cf0abSJim Walker    }
159264cf0abSJim Walker    if (!pOptions.outputFilename && !pOptions.options.test(Options::Status)) {
160264cf0abSJim Walker        usage();
161264cf0abSJim Walker        throw std::invalid_argument("Missing -o");
162264cf0abSJim Walker    }
163264cf0abSJim Walker    if (pOptions.outputFilename && pOptions.options.test(Options::Status)) {
164264cf0abSJim Walker        usage();
165264cf0abSJim Walker        throw std::invalid_argument("-o with -s is not allowed");
166264cf0abSJim Walker    }
167264cf0abSJim Walker
168264cf0abSJim Walker    return pOptions;
169264cf0abSJim Walker}
170264cf0abSJim Walker
171264cf0abSJim Walkerint main(int argc, char** argv) {
172264cf0abSJim Walker    bool success = true;
173264cf0abSJim Walker    try {
174264cf0abSJim Walker        auto options = parseArguments(argc, argv);
175264cf0abSJim Walker        Collections::InputCouchFile input(options.options,
176264cf0abSJim Walker                                          options.inputFilename);
177264cf0abSJim Walker
178264cf0abSJim Walker        if (options.options.test(Options::Status)) {
179264cf0abSJim Walker            runStatus(input);
180264cf0abSJim Walker        } else {
181f9266326SJim Walker            success = runUpgrade(options, input);
182264cf0abSJim Walker        }
183264cf0abSJim Walker    } catch (const std::exception& e) {
184264cf0abSJim Walker        success = false;
185264cf0abSJim Walker        std::cerr << "An exception occurred: " << e.what() << std::endl;
186264cf0abSJim Walker    }
187264cf0abSJim Walker
188264cf0abSJim Walker    if (!success) {
189264cf0abSJim Walker        std::cerr << "Terminating with exit code 1\n";
190264cf0abSJim Walker    }
191264cf0abSJim Walker
192264cf0abSJim Walker    return success ? EXIT_SUCCESS : EXIT_FAILURE;
193264cf0abSJim Walker}