0.9.8.10
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
TranslatorTask.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2007-2015 Hypertable, Inc.
3  *
4  * This file is part of Hypertable.
5  *
6  * Hypertable is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; version 3 of the
9  * License, or any later version.
10  *
11  * Hypertable is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21 
26 
27 #include <Common/Compat.h>
28 
29 #include "TokenizerTools.h"
30 #include "TranslatorTask.h"
31 
32 #include <Common/Error.h>
33 #include <Common/Logger.h>
34 #include <Common/String.h>
35 #include <Common/System.h>
36 
37 #include <boost/algorithm/string.hpp>
38 #include <boost/tokenizer.hpp>
39 
40 #include <cctype>
41 #include <vector>
42 
43 using namespace Hypertable;
44 using namespace Hypertable::ClusterDefinitionFile;
45 using namespace boost;
46 using namespace std;
47 
48 namespace {
49 
50  const char *builtin_task_name[] = {
51  "CLUSTER_BUILTIN_display_line",
52  "show_variables",
53  "with",
54  nullptr
55  };
56 
57  const string determine_trailing_indentation(const string &text) {
58  string indentation;
59  if (!text.empty()) {
60  const char *base = text.c_str();
61  const char *ptr = base + (text.length() - 1);
62  size_t count = 0;
63  while (ptr >= base && isspace(*ptr) && *ptr != '\n' && *ptr != '\r') {
64  count++;
65  ptr--;
66  }
67  indentation.append(ptr+1);
68  }
69  // If can't determine indentation, make it two spaces
70  if (indentation.empty())
71  indentation.append(" ");
72  return indentation;
73  }
74 
75  bool translate_ssh_statement(const char *base, const char *end,
76  const string &indentation,
77  const char **nextp, string &ssh_command,
78  string &errmsg) {
79  HT_ASSERT(strncmp(base, "ssh:", 4) == 0);
80  errmsg.clear();
81 
82  base += 4;
83 
84  const char *open_curly = base;
85  while (open_curly < end && *open_curly != '{')
86  open_curly++;
87 
88  const char *close_curly;
89  if (open_curly == end || !TokenizerTools::find_end_char(open_curly, &close_curly)) {
90  errmsg.append("curly brace not found");
91  return false;
92  }
93 
94  // parse options
95  string options;
96  {
97  string text(base, open_curly-base);
98  char_separator<char> sep(" \t\n\r");
99  tokenizer<char_separator<char>> tokens(text, sep);
100  for (const auto& token : tokens) {
101  if (!strncmp(token.c_str(), "random-start-delay=", 19)) {
102  if (!TokenizerTools::is_number(token.substr(19))) {
103  errmsg.append("invalid random-start-delay argument");
104  return false;
105  }
106  options.append(" --");
107  options.append(token.substr(0, 18));
108  options.append(" ");
109  options.append(token.substr(19));
110  }
111  else if (token.compare("in-series") == 0) {
112  options.append(" --");
113  options.append(token);
114  }
115  else {
116  errmsg.append(Hypertable::format("Unknown option '%s'", token.c_str()));
117  return false;
118  }
119  }
120  }
121 
122  // build ssh command
123  ssh_command.clear();
124  ssh_command.append(Hypertable::format("%s/bin/ht ssh%s \" ${_SSH_HOSTS}\" \"",
125  System::install_dir.c_str(), options.c_str()));
126  open_curly++;
127  string content(open_curly, close_curly-open_curly);
128  trim(content);
129 
130  string escaped_content;
131  escaped_content.reserve(content.length()+32);
132  for (const char *ptr = content.c_str(); *ptr; ptr++) {
133  if (*ptr == '"')
134  escaped_content.append(1, '\\');
135  escaped_content.append(1, *ptr);
136  }
137 
138  ssh_command.append(escaped_content);
139  ssh_command.append("\"\n");
140  ssh_command.append(indentation);
141  ssh_command.append("if [ $? -ne 0 ]; then\n");
142  ssh_command.append(indentation);
143  ssh_command.append(" exit 1\n");
144  ssh_command.append(indentation);
145  ssh_command.append("fi");
146 
147  *nextp = close_curly + 1;
148 
149  return true;
150  }
151 
152 }
153 
154 
155 
156 
158  string translated_text;
159  string description;
160 
161  const char *base = m_text.c_str();
162  const char *end;
163  string line;
164 
165  while (*base) {
166 
167  end = strchr(base, '\n');
168  line.clear();
169  if (end == 0)
170  line.append(base);
171  else
172  line.append(base, end-base);
173  boost::trim(line);
174 
175  if (line[0] == '#') {
176  line = line.substr(1);
177  trim(line);
178  if (description.empty())
179  description.append(line);
180  else if (description[description.length()-1] == '.') {
181  description.append(" ");
182  description.append(line);
183  }
184  else {
185  description.append(" ");
186  description.append(line);
187  }
188  }
189  else
190  break;
191  if (end)
192  base = end;
193  else
194  base += strlen(base);
195  if (*base)
196  base++;
197  }
198 
199  // Do variable substitution
200  while (TokenizerTools::substitute_variables(description, description,
201  context.symbols))
202  ;
203 
204  if (*base == 0)
205  HT_THROWF(Error::SYNTAX_ERROR, "Bad task definition on line %d of '%s'",
206  (int)m_lineno, m_fname.c_str());
207 
208  const char *ptr;
209  if ((ptr = strchr(base, '{')) == 0)
210  HT_THROWF(Error::SYNTAX_ERROR, "Bad task definition on line %d of '%s'",
211  (int)m_lineno, m_fname.c_str());
212 
213  if (!TokenizerTools::find_end_char(ptr, &end))
215  "Missing terminating '}' in task definition on line %d of '%s'",
216  (int)m_lineno, m_fname.c_str());
217 
218  string text(base, ptr-base);
219 
220  vector<string> words;
221  {
222  char_separator<char> sep(" ");
223  tokenizer<char_separator<char>> tokens(text, sep);
224  for (const auto& t : tokens)
225  words.push_back(t);
226  }
227 
228  if (words.size() < 2 || words.size() == 3 || words[0].compare("task:") != 0 ||
229  (words.size() >= 3 && words[2].compare("roles:") != 0))
230  HT_THROWF(Error::SYNTAX_ERROR, "Bad task definition on line %d of '%s'",
231  (int)m_lineno, m_fname.c_str());
232 
233  string task_name(words[1]);
234 
235  for (size_t i=0; builtin_task_name[i]; i++) {
236  if (task_name.compare(builtin_task_name[i]) == 0)
238  "Task name '%s' conflicts with built-in on line %d of '%s'",
239  task_name.c_str(), (int)m_lineno, m_fname.c_str());
240  }
241 
242  if (!TokenizerTools::is_valid_identifier(task_name))
243  HT_THROWF(Error::SYNTAX_ERROR, "Invalid task name (%s) on line %d of '%s'",
244  task_name.c_str(), (int)m_lineno, m_fname.c_str());
245 
246  translated_text.append(task_name);
247  translated_text.append(" () {\n local _SSH_HOSTS=\"");;
248 
249  string task_roles;
250  if (words.size() > 3) {
251  text.clear();
252  for (size_t i=3; i<words.size(); i++) {
253  text.append(words[i]);
254  text.append(" ");
255  }
256  char_separator<char> sep(" ,");
257  tokenizer<char_separator<char>> tokens(text, sep);
258  bool first = true;
259  for (const auto& t : tokens) {
260  if (context.roles.count(t) == 0)
261  HT_THROWF(Error::SYNTAX_ERROR, "Undefined role (%s) on line %d of '%s'",
262  t.c_str(), (int)m_lineno, m_fname.c_str());
263  if (first)
264  first = false;
265  else
266  translated_text.append(" + ");
267  translated_text.append("(${ROLE_");
268  translated_text.append(t);
269  translated_text.append("})");
270  if (!task_roles.empty())
271  task_roles.append(", ");
272  task_roles.append(t);
273  }
274  }
275  else {
276  bool first = true;
277  for (auto & role : context.roles) {
278  if (first)
279  first = false;
280  else
281  translated_text.append(" + ");
282  translated_text.append("(${ROLE_");
283  translated_text.append(role);
284  translated_text.append("})");
285  }
286  }
287  translated_text.append("\"\n");
288  translated_text.append(" if [ $# -gt 0 ] && [ $1 == \"on\" ]; then\n");
289  translated_text.append(" shift\n");
290  translated_text.append(" if [ $# -eq 0 ]; then\n");
291  translated_text.append(" echo \"Missing host specification in 'on' argument\"\n");
292  translated_text.append(" exit 1\n");
293  translated_text.append(" else\n");
294  translated_text.append(" _SSH_HOSTS=\"$1\"\n");
295  translated_text.append(" shift\n");
296  translated_text.append(" fi\n");
297  translated_text.append(" fi\n");
298  translated_text.append(" echo \"");
299  translated_text.append(task_name);
300  translated_text.append(" $@\"\n ");
301 
302  size_t lineno = m_lineno + TokenizerTools::count_newlines(m_text.c_str(), base);
303 
304  // skip '{' and whitespace
305  base = ptr + 1;
306  while (base < end && isspace(*base)) {
307  if (*base == '\n')
308  lineno++;
309  base++;
310  }
311 
312  HT_ASSERT(base <= end);
313 
314  string task_body;
315  string ssh_command;
316  string error_msg;
317  size_t offset;
318 
319  // Check for missing ':' character
320  if (TokenizerTools::find_token("ssh", base, end, &offset)) {
321  size_t tmp_lineno = lineno + TokenizerTools::count_newlines(base, base+offset);
322  ptr = base + offset + 3;
323  while (*ptr && isspace(*ptr))
324  ptr++;
325  if (*ptr == '{' || *ptr == 0 || !strncmp(ptr, "random-start-delay=", 19) ||
326  !strncmp(ptr, "in-series", 9))
327  HT_THROWF(Error::SYNTAX_ERROR,"Invalid ssh: statement on line %d of '%s'",
328  (int)tmp_lineno, m_fname.c_str());
329  }
330 
331  while (TokenizerTools::find_token("ssh:", base, end, &offset)) {
332  lineno += TokenizerTools::count_newlines(base, base+offset);
333  task_body.append(base, offset);
334  base += offset;
335  string indentation = determine_trailing_indentation(task_body);
336  if (!translate_ssh_statement(base, end, indentation, &ptr, ssh_command, error_msg))
337  HT_THROWF(Error::SYNTAX_ERROR,"Invalid ssh: statement (%s) on line %d of '%s'",
338  error_msg.c_str(), (int)lineno, m_fname.c_str());
339  lineno += TokenizerTools::count_newlines(base, ptr);
340  task_body.append(ssh_command);
341  base = ptr;
342  }
343 
344  if (base < end) {
345  string content(base, end-base);
346  trim_right(content);
347  task_body.append(content);
348  }
349 
350  translated_text.append(task_body);
351 
352  translated_text.append("\n}\n");
353 
354  context.tasks[task_name] = description;
355  context.task_roles[task_name] = task_roles;
356 
357  return translated_text;
358 }
359 
Declarations for TranslatorTask.
Retrieves system information (hardware, installation directory, etc)
bool find_token(const string &token, const char *base, const char *end, size_t *offsetp)
Finds a string token in a block of code.
bool substitute_variables(const string &input, string &output, map< string, string > &vmap)
Does variable sustitution in a block of text.
Boost library.
Definition: Properties.cc:39
String format(const char *fmt,...)
Returns a String using printf like format facilities Vanilla snprintf is about 1.5x faster than this...
Definition: String.cc:37
STL namespace.
bool find_end_char(const char *base, const char **endp, size_t *linep)
Skips to end of block or quoted string in code.
size_t count_newlines(const char *base, const char *end)
Counts number of newlines in text.
bool is_valid_identifier(const string &name)
Checks if name is a valid bash identifier.
#define HT_ASSERT(_e_)
Definition: Logger.h:396
Logging routines and macros.
const string translate(TranslationContext &context) override
Translates a task definition.
Compatibility Macros for C/C++.
Hypertable definitions
map< string, string > symbols
Map of variable names to default values.
#define HT_THROWF(_code_, _fmt_,...)
Definition: Error.h:490
static String install_dir
The installation directory.
Definition: System.h:114
bool is_number(const string &str)
Checks if string is an ASCII number.
Declarations for TokenizerTools.
A String class based on std::string.
Context used to translate cluster definition statements.
Cluster definition file translation definitions.
Definition: Compiler.h:35
Error codes, Exception handling, error logging.
map< string, string > tasks
Map of tasks to descriptive text.