aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTester.java
blob: 1db74d66c8c7c0c5857df574f21b89ecfe08f94f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.yum;

import com.yahoo.vespa.hosted.node.admin.task.util.process.TestChildProcess2;
import com.yahoo.vespa.hosted.node.admin.task.util.process.TestTerminal;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * A {@link Yum} tester that simplifies testing interaction with yum.
 *
 * @author freva
 */
public class YumTester extends Yum {

    private final TestTerminal terminal;

    public YumTester(TestTerminal terminal) {
        super(terminal);
        this.terminal = terminal;
    }

    public GenericYumCommandExpectation expectInstall(String... packages) {
        return new GenericYumCommandExpectation(CommandType.install, packages);
    }

    public GenericYumCommandExpectation expectUpdate(String... packages) {
        return new GenericYumCommandExpectation(CommandType.upgrade, packages);
    }

    public GenericYumCommandExpectation expectRemove(String... packages) {
        return new GenericYumCommandExpectation(CommandType.remove, packages);
    }

    public InstallFixedCommandExpectation expectInstallFixedVersion(String yumPackage) {
        return new InstallFixedCommandExpectation(yumPackage);
    }

    public DeleteVersionLockCommandExpectation expectDeleteVersionLock(String yumPackage) {
        return new DeleteVersionLockCommandExpectation(yumPackage);
    }

    public QueryInstalledExpectation expectQueryInstalled(String packageName) {
        return new QueryInstalledExpectation(packageName);
    }


    public class GenericYumCommandExpectation {
        private final CommandType commandType;
        protected final List<YumPackageName> packages;
        private List<String> disableRepos = List.of();
        private List<String> enableRepos = List.of();

        private GenericYumCommandExpectation(CommandType commandType, String... packages) {
            this.commandType = commandType;
            this.packages = Stream.of(packages).map(YumPackageName::fromString).toList();
        }

        public GenericYumCommandExpectation withDisableRepo(String... repo) {
            this.disableRepos = List.of(repo);
            return this;
        }

        public GenericYumCommandExpectation withEnableRepo(String... repo) {
            this.enableRepos = List.of(repo);
            return this;
        }

        /** Mock the return value of the converge(TaskContext) method for this operation (true iff system was modified) */
        public YumTester andReturn(boolean value) {
            if (value) return execute("Success");
            return switch (commandType) {
                case deleteVersionLock, installFixed, install -> execute("Nothing to do");
                case upgrade -> execute("No packages marked for update");
                case remove -> execute("No Packages marked for removal");
                default -> throw new IllegalArgumentException("Unknown command type: " + commandType);
            };
        }

        private YumTester execute(String output) {
            if (commandType == CommandType.install)
                terminal.interceptCommand("rpm query", cmd -> new TestChildProcess2(1, "Not installed"));
            if (commandType == CommandType.remove) { // Pretend the first package is installed so we can continue to yum commands
                YumPackageName pkg = packages.get(0);
                terminal.interceptCommand("rpm query", cmd -> new TestChildProcess2(0, String.join("\n",
                        pkg.getName(),
                        pkg.getEpoch().orElse("(none)"),
                        pkg.getVersion().orElse("1.2.3"),
                        pkg.getRelease().orElse("1"),
                        pkg.getArchitecture().orElse("(none)"))));
            }

            StringBuilder cmd = new StringBuilder();
            cmd.append("yum ").append(commandType.command);
            if (commandType != CommandType.deleteVersionLock) {
                cmd.append(" --assumeyes");
                // mimic maybeEscapeArgument() in CommandLine
                disableRepos.forEach(repo -> {
                    if (repo.equals("*")) cmd.append(" \"--disablerepo=").append(repo).append("\"");
                    else cmd.append(" --disablerepo=").append(repo);
                });
                enableRepos.forEach(repo -> cmd.append(" --enablerepo=").append(repo));
            }
            if (commandType == CommandType.install && packages.size() > 1)
                cmd.append(" --setopt skip_missing_names_on_install=False");
            if (commandType == CommandType.upgrade && packages.size() > 1)
                cmd.append(" --setopt skip_missing_names_on_update=False");
            packages.forEach(pkg -> {
                String name = pkg.toName();
                if (name.contains("(") || name.contains(")")) { // Ugly hack to handle implicit quoting done in com.yahoo.vespa.hosted.node.admin.task.util.process.CommandLine
                    name = "\"" + name + "\"";
                }
                cmd.append(" ").append(name);
            });
            cmd.append(" 2>&1");

            terminal.expectCommand(cmd.toString(), 0, output);
            return YumTester.this;
        }
    }

    public class InstallFixedCommandExpectation extends GenericYumCommandExpectation {

        private InstallFixedCommandExpectation(String yumPackage) {
            super(CommandType.installFixed, yumPackage);
        }

        @Override
        public YumTester andReturn(boolean value) {
            terminal.expectCommand("yum versionlock list 2>&1", 0, packages.get(0).toVersionLockName());
            return super.andReturn(value);
        }

    }

    public class DeleteVersionLockCommandExpectation extends GenericYumCommandExpectation {

        private DeleteVersionLockCommandExpectation(String yumPackage) {
            super(CommandType.deleteVersionLock, yumPackage);
        }

    }

    public class QueryInstalledExpectation {
        private final String packageName;

        public QueryInstalledExpectation(String packageName) {
            this.packageName = packageName;
        }

        /** Package name to return or null if package is not installed */
        public YumTester andReturn(YumPackageName yumPackage) {
            TestChildProcess2 process = new TestChildProcess2(
                    yumPackage == null ? 1 : 0,
                    yumPackage == null ? "not installed" : String.join("\n",
                            yumPackage.getName(),
                            yumPackage.getEpoch().orElse("(none)"),
                            yumPackage.getVersion().orElseThrow(() -> new IllegalArgumentException("Version must be set")),
                            yumPackage.getRelease().orElseThrow(() -> new IllegalArgumentException("Release must be set")),
                            yumPackage.getArchitecture().orElse("(none)")));

            terminal.expectCommand("rpm -q " + packageName + " --queryformat \"%{NAME}\\\\n%{EPOCH}\\\\n%{VERSION}\\\\n%{RELEASE}\\\\n%{ARCH}\" 2>&1", process);
            return YumTester.this;
        }
    }

    private enum CommandType {
        install("install"), upgrade("upgrade"), remove("remove"), installFixed("install"), deleteVersionLock("versionlock delete");

        private final String command;
        CommandType(String command) {
            this.command = command;
        }
    }

}