http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/StringUtils.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/StringUtils.java b/utils/src/main/java/com/cloud/utils/StringUtils.java new file mode 100644 index 0000000..c598be8 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/StringUtils.java @@ -0,0 +1,323 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.owasp.esapi.StringUtilities; + +public class StringUtils { + private static final char[] hexChar = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static Charset preferredACSCharset; + private static final String UTF8 = "UTF-8"; + + static { + if (isUtf8Supported()) { + preferredACSCharset = Charset.forName(UTF8); + } else { + preferredACSCharset = Charset.defaultCharset(); + } + } + + public static Charset getPreferredCharset() { + return preferredACSCharset; + } + + public static boolean isUtf8Supported() { + return Charset.isSupported(UTF8); + } + + protected static Charset getDefaultCharset() { + return Charset.defaultCharset(); + } + + public static String join(final Iterable<? extends Object> iterable, final String delim) { + final StringBuilder sb = new StringBuilder(); + if (iterable != null) { + final Iterator<? extends Object> iter = iterable.iterator(); + if (iter.hasNext()) { + final Object next = iter.next(); + sb.append(next.toString()); + } + while (iter.hasNext()) { + final Object next = iter.next(); + sb.append(delim + next.toString()); + } + } + return sb.toString(); + } + + public static String join(final String delimiter, final Object... components) { + return org.apache.commons.lang.StringUtils.join(components, delimiter); + } + + public static boolean isNotBlank(final String str) { + if (str != null && str.trim().length() > 0) { + return true; + } + + return false; + } + + public static String cleanupTags(String tags) { + if (tags != null) { + final String[] tokens = tags.split(","); + final StringBuilder t = new StringBuilder(); + for (int i = 0; i < tokens.length; i++) { + t.append(tokens[i].trim()).append(","); + } + t.delete(t.length() - 1, t.length()); + tags = t.toString(); + } + + return tags; + } + + /** + * @param tags + * @return List of tags + */ + public static List<String> csvTagsToList(final String tags) { + final List<String> tagsList = new ArrayList<String>(); + + if (tags != null) { + final String[] tokens = tags.split(","); + for (int i = 0; i < tokens.length; i++) { + tagsList.add(tokens[i].trim()); + } + } + + return tagsList; + } + + /** + * Converts a List of tags to a comma separated list + * @param tags + * @return String containing a comma separated list of tags + */ + + public static String listToCsvTags(final List<String> tagsList) { + final StringBuilder tags = new StringBuilder(); + if (tagsList.size() > 0) { + for (int i = 0; i < tagsList.size(); i++) { + tags.append(tagsList.get(i)); + if (i != tagsList.size() - 1) { + tags.append(','); + } + } + } + + return tags.toString(); + } + + public static String getExceptionStackInfo(final Throwable e) { + final StringBuffer sb = new StringBuffer(); + + sb.append(e.toString()).append("\n"); + final StackTraceElement[] elemnents = e.getStackTrace(); + for (final StackTraceElement element : elemnents) { + sb.append(element.getClassName()).append("."); + sb.append(element.getMethodName()).append("("); + sb.append(element.getFileName()).append(":"); + sb.append(element.getLineNumber()).append(")"); + sb.append("\n"); + } + + return sb.toString(); + } + + public static String unicodeEscape(final String s) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + final char c = s.charAt(i); + if (c >> 7 > 0) { + sb.append("\\u"); + sb.append(hexChar[c >> 12 & 0xF]); // append the hex character for the left-most 4-bits + sb.append(hexChar[c >> 8 & 0xF]); // hex for the second group of 4-bits from the left + sb.append(hexChar[c >> 4 & 0xF]); // hex for the third group + sb.append(hexChar[c & 0xF]); // hex for the last group, e.g., the right most 4-bits + } else { + sb.append(c); + } + } + return sb.toString(); + } + + public static String getMaskedPasswordForDisplay(final String password) { + if (password == null || password.isEmpty()) { + return "*"; + } + + final StringBuffer sb = new StringBuffer(); + sb.append(password.charAt(0)); + for (int i = 1; i < password.length(); i++) { + sb.append("*"); + } + + return sb.toString(); + } + + // removes a password request param and it's value, also considering password is in query parameter value which has been url encoded + private static final Pattern REGEX_PASSWORD_QUERYSTRING = Pattern.compile("(&|%26)?[^(&|%26)]*((p|P)assword|accesskey|secretkey)(=|%3D).*?(?=(%26|[&'\"]|$))"); + + // removes a password/accesskey/ property from a response json object + private static final Pattern REGEX_PASSWORD_JSON = Pattern.compile("\"((p|P)assword|accesskey|secretkey)\":\\s?\".*?\",?"); + + private static final Pattern REGEX_PASSWORD_DETAILS = Pattern.compile("(&|%26)?details(\\[|%5B)\\d*(\\]|%5D)\\.key(=|%3D)((p|P)assword|accesskey|secretkey)(?=(%26|[&'\"]))"); + + private static final Pattern REGEX_PASSWORD_DETAILS_INDEX = Pattern.compile("details(\\[|%5B)\\d*(\\]|%5D)"); + + private static final Pattern REGEX_REDUNDANT_AND = Pattern.compile("(&|%26)(&|%26)+"); + + // Responsible for stripping sensitive content from request and response strings + public static String cleanString(final String stringToClean) { + String cleanResult = ""; + if (stringToClean != null) { + cleanResult = REGEX_PASSWORD_QUERYSTRING.matcher(stringToClean).replaceAll(""); + cleanResult = REGEX_PASSWORD_JSON.matcher(cleanResult).replaceAll(""); + final Matcher detailsMatcher = REGEX_PASSWORD_DETAILS.matcher(cleanResult); + while (detailsMatcher.find()) { + final Matcher detailsIndexMatcher = REGEX_PASSWORD_DETAILS_INDEX.matcher(detailsMatcher.group()); + if (detailsIndexMatcher.find()) { + cleanResult = cleanDetails(cleanResult, detailsIndexMatcher.group()); + } + } + } + return cleanResult; + } + + public static String cleanDetails(final String stringToClean, final String detailsIndexSting) { + String cleanResult = stringToClean; + for (final String log : stringToClean.split("&|%26")) { + if (log.contains(detailsIndexSting)) { + cleanResult = cleanResult.replace(log, ""); + } + } + cleanResult = REGEX_REDUNDANT_AND.matcher(cleanResult).replaceAll("&"); + return cleanResult; + } + + public static boolean areTagsEqual(final String tags1, final String tags2) { + if (tags1 == null && tags2 == null) { + return true; + } + + if (tags1 != null && tags2 == null) { + return false; + } + + if (tags1 == null && tags2 != null) { + return false; + } + + final String delimiter = ","; + + final List<String> lstTags1 = new ArrayList<String>(); + final String[] aTags1 = tags1.split(delimiter); + + for (final String tag1 : aTags1) { + lstTags1.add(tag1.toLowerCase()); + } + + final List<String> lstTags2 = new ArrayList<String>(); + final String[] aTags2 = tags2.split(delimiter); + + for (final String tag2 : aTags2) { + lstTags2.add(tag2.toLowerCase()); + } + + return lstTags1.containsAll(lstTags2) && lstTags2.containsAll(lstTags1); + } + + public static String stripControlCharacters(final String s) { + return StringUtilities.stripControls(s); + } + + public static int formatForOutput(final String text, final int start, final int columns, final char separator) { + if (start >= text.length()) { + return -1; + } + + int end = start + columns; + if (end > text.length()) { + end = text.length(); + } + final String searchable = text.substring(start, end); + final int found = searchable.lastIndexOf(separator); + return found > 0 ? found : end - start; + } + + public static Map<String, String> stringToMap(final String s) { + final Map<String, String> map = new HashMap<String, String>(); + final String[] elements = s.split(";"); + for (final String parts : elements) { + final String[] keyValue = parts.split(":"); + map.put(keyValue[0], keyValue[1]); + } + return map; + } + + public static String mapToString(final Map<String, String> map) { + String s = ""; + for (final Map.Entry<String, String> entry : map.entrySet()) { + s += entry.getKey() + ":" + entry.getValue() + ";"; + } + if (s.length() > 0) { + s = s.substring(0, s.length() - 1); + } + return s; + } + + public static <T> List<T> applyPagination(final List<T> originalList, final Long startIndex, final Long pageSizeVal) { + // Most likely pageSize will never exceed int value, and we need integer to partition the listToReturn + final boolean applyPagination = startIndex != null && pageSizeVal != null + && startIndex <= Integer.MAX_VALUE && startIndex >= Integer.MIN_VALUE && pageSizeVal <= Integer.MAX_VALUE + && pageSizeVal >= Integer.MIN_VALUE; + List<T> listWPagination = null; + if (applyPagination) { + listWPagination = new ArrayList<>(); + final int index = startIndex.intValue() == 0 ? 0 : startIndex.intValue() / pageSizeVal.intValue(); + final List<List<T>> partitions = StringUtils.partitionList(originalList, pageSizeVal.intValue()); + if (index < partitions.size()) { + listWPagination = partitions.get(index); + } + } + return listWPagination; + } + + private static <T> List<List<T>> partitionList(final List<T> originalList, final int chunkSize) { + final List<List<T>> listOfChunks = new ArrayList<List<T>>(); + for (int i = 0; i < originalList.size() / chunkSize; i++) { + listOfChunks.add(originalList.subList(i * chunkSize, i * chunkSize + chunkSize)); + } + if (originalList.size() % chunkSize != 0) { + listOfChunks.add(originalList.subList(originalList.size() - originalList.size() % chunkSize, originalList.size())); + } + return listOfChunks; + } +}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/SwiftUtil.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/SwiftUtil.java b/utils/src/main/java/com/cloud/utils/SwiftUtil.java new file mode 100644 index 0000000..1136818 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/SwiftUtil.java @@ -0,0 +1,239 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils; + +import java.io.File; +import java.util.Arrays; +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.OutputInterpreter; +import com.cloud.utils.script.Script; + +public class SwiftUtil { + private static Logger logger = Logger.getLogger(SwiftUtil.class); + private static final long SWIFT_MAX_SIZE = 5L * 1024L * 1024L * 1024L; + + public interface SwiftClientCfg { + String getAccount(); + + String getUserName(); + + String getKey(); + + String getEndPoint(); + } + + private static String getSwiftCLIPath() { + String swiftCLI = Script.findScript("scripts/storage/secondary", "swift"); + if (swiftCLI == null) { + logger.debug("Can't find swift cli at scripts/storage/secondary/swift"); + throw new CloudRuntimeException("Can't find swift cli at scripts/storage/secondary/swift"); + } + return swiftCLI; + } + + public static boolean postMeta(SwiftClientCfg cfg, String container, String object, Map<String, String> metas) { + String swiftCli = getSwiftCLIPath(); + StringBuilder cms = new StringBuilder(); + for (Map.Entry<String, String> entry : metas.entrySet()) { + cms.append(" -m "); + cms.append(entry.getKey()); + cms.append(":"); + cms.append(entry.getValue()); + cms.append(" "); + } + Script command = new Script("/bin/bash", logger); + command.add("-c"); + command.add("/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + " -K " + cfg.getKey() + " post " + + container + " " + object + " " + cms.toString()); + OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); + String result = command.execute(parser); + if (result != null) { + throw new CloudRuntimeException("Failed to post meta" + result); + } + return true; + } + + public static String putObject(SwiftClientCfg cfg, File srcFile, String container, String fileName) { + String swiftCli = getSwiftCLIPath(); + if (fileName == null) { + fileName = srcFile.getName(); + } + String srcDirectory = srcFile.getParent(); + Script command = new Script("/bin/bash", logger); + long size = srcFile.length(); + command.add("-c"); + if (size <= SWIFT_MAX_SIZE) { + command.add("cd " + srcDirectory + ";/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + + " -K " + cfg.getKey() + " upload " + container + " " + fileName); + } else { + command.add("cd " + srcDirectory + ";/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + + " -K " + cfg.getKey() + " upload -S " + SWIFT_MAX_SIZE + " " + container + " " + fileName); + } + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + String result = command.execute(parser); + if (result != null) { + throw new CloudRuntimeException("Failed to upload file: " + result); + } + + if (parser.getLines() != null) { + String[] lines = parser.getLines().split("\\n"); + for (String line : lines) { + if (line.contains("Errno") || line.contains("failed") || line.contains("not found")) { + throw new CloudRuntimeException("Failed to upload file: " + Arrays.toString(lines)); + } + } + } + + return container + File.separator + srcFile.getName(); + } + + private static StringBuilder buildSwiftCmd(SwiftClientCfg swift) { + String swiftCli = getSwiftCLIPath(); + StringBuilder sb = new StringBuilder(); + sb.append(" /usr/bin/python "); + sb.append(swiftCli); + sb.append(" -A "); + sb.append(swift.getEndPoint()); + sb.append(" -U "); + sb.append(swift.getAccount()); + sb.append(":"); + sb.append(swift.getUserName()); + sb.append(" -K "); + sb.append(swift.getKey()); + sb.append(" "); + return sb; + } + + public static String[] list(SwiftClientCfg swift, String container, String rFilename) { + getSwiftCLIPath(); + Script command = new Script("/bin/bash", logger); + command.add("-c"); + + StringBuilder swiftCmdBuilder = buildSwiftCmd(swift); + swiftCmdBuilder.append(" list "); + swiftCmdBuilder.append(container); + + if (rFilename != null) { + swiftCmdBuilder.append(" -p "); + swiftCmdBuilder.append(rFilename); + } + + command.add(swiftCmdBuilder.toString()); + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + String result = command.execute(parser); + if (result == null && parser.getLines() != null && !parser.getLines().equalsIgnoreCase("")) { + String[] lines = parser.getLines().split("\\n"); + return lines; + } else { + if (result != null) { + String errMsg = "swiftList failed , err=" + result; + logger.debug("Failed to list " + errMsg); + } else { + String errMsg = "swiftList failed, no lines returns"; + logger.debug("Failed to list " + errMsg); + } + } + return new String[0]; + } + + public static File getObject(SwiftClientCfg cfg, File destDirectory, String swiftPath) { + int firstIndexOfSeparator = swiftPath.indexOf(File.separator); + String container = swiftPath.substring(0, firstIndexOfSeparator); + String srcPath = swiftPath.substring(firstIndexOfSeparator + 1); + String destFilePath = null; + if (destDirectory.isDirectory()) { + destFilePath = destDirectory.getAbsolutePath() + File.separator + srcPath; + } else { + destFilePath = destDirectory.getAbsolutePath(); + } + String swiftCli = getSwiftCLIPath(); + Script command = new Script("/bin/bash", logger); + command.add("-c"); + command.add("/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + " -K " + cfg.getKey() + + " download " + container + " " + srcPath + " -o " + destFilePath); + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + String result = command.execute(parser); + if (result != null) { + String errMsg = "swiftDownload failed err=" + result; + logger.debug(errMsg); + throw new CloudRuntimeException("failed to get object: " + swiftPath); + } + if (parser.getLines() != null) { + String[] lines = parser.getLines().split("\\n"); + for (String line : lines) { + if (line.contains("Errno") || line.contains("failed")) { + String errMsg = "swiftDownload failed , err=" + Arrays.toString(lines); + logger.debug(errMsg); + throw new CloudRuntimeException("Failed to get object: " + swiftPath); + } + } + } + return new File(destFilePath); + } + + public static String getContainerName(String type, Long id) { + if (type.startsWith("T")) { + return "T-" + id; + } else if (type.startsWith("S")) { + return "S-" + id; + } else if (type.startsWith("V")) { + return "V-" + id; + } + return null; + } + + public static String[] splitSwiftPath(String path) { + int index = path.indexOf(File.separator); + if (index == -1) { + return null; + } + String[] paths = new String[2]; + paths[0] = path.substring(0, index); + paths[1] = path.substring(index + 1); + return paths; + } + + public static boolean deleteObject(SwiftClientCfg cfg, String path) { + Script command = new Script("/bin/bash", logger); + command.add("-c"); + + String[] paths = splitSwiftPath(path); + if (paths == null) { + return false; + } + String container = paths[0]; + String objectName = paths[1]; + + StringBuilder swiftCmdBuilder = buildSwiftCmd(cfg); + swiftCmdBuilder.append(" delete "); + swiftCmdBuilder.append(container); + swiftCmdBuilder.append(" "); + swiftCmdBuilder.append(objectName); + + command.add(swiftCmdBuilder.toString()); + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + command.execute(parser); + return true; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/Ternary.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/Ternary.java b/utils/src/main/java/com/cloud/utils/Ternary.java new file mode 100644 index 0000000..fdc2fc9 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/Ternary.java @@ -0,0 +1,85 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils; + +public class Ternary<T, U, V> { + private T t; + private U u; + private V v; + + public Ternary(T t, U u, V v) { + this.t = t; + this.u = u; + this.v = v; + } + + public T first() { + return t; + } + + public void first(T t) { + this.t = t; + } + + public U second() { + return u; + } + + public void second(U u) { + this.u = u; + } + + public V third() { + return v; + } + + public void third(V v) { + this.v = v; + } + + @Override + // Note: This means any two pairs with null for both values will match each + // other but what can I do? This is due to stupid type erasure. + public + int hashCode() { + return (t != null ? t.hashCode() : 0) | (u != null ? u.hashCode() : 0) | (v != null ? v.hashCode() : 0); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Ternary)) { + return false; + } + Ternary<?, ?, ?> that = (Ternary<?, ?, ?>)obj; + return (t != null ? t.equals(that.t) : that.t == null) && (u != null ? u.equals(that.u) : that.u == null) && (v != null ? v.equals(that.v) : that.v == null); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("T["); + b.append(t != null ? t.toString() : "null"); + b.append(":"); + b.append(u != null ? u.toString() : "null"); + b.append(":"); + b.append(v != null ? v.toString() : "null"); + b.append("]"); + return b.toString(); + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/UriUtils.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java b/utils/src/main/java/com/cloud/utils/UriUtils.java new file mode 100644 index 0000000..631c629 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/UriUtils.java @@ -0,0 +1,394 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.StringTokenizer; + +import javax.net.ssl.HttpsURLConnection; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.util.URIUtil; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.client.utils.URLEncodedUtils; + +import org.apache.http.message.BasicNameValuePair; +import org.apache.log4j.Logger; + +import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.exception.CloudRuntimeException; + +public class UriUtils { + + public static final Logger s_logger = Logger.getLogger(UriUtils.class.getName()); + + public static String formNfsUri(String host, String path) { + try { + URI uri = new URI("nfs", host, path, null); + return uri.toString(); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Unable to form nfs URI: " + host + " - " + path); + } + } + + public static String formIscsiUri(String host, String iqn, Integer lun) { + try { + String path = iqn; + if (lun != null) { + path += "/" + lun.toString(); + } + URI uri = new URI("iscsi", host, path, null); + return uri.toString(); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Unable to form iscsi URI: " + host + " - " + iqn + " - " + lun); + } + } + + public static String formFileUri(String path) { + File file = new File(path); + + return file.toURI().toString(); + } + + // a simple URI component helper (Note: it does not deal with URI paramemeter area) + public static String encodeURIComponent(String url) { + int schemeTail = url.indexOf("://"); + + int pathStart = 0; + if (schemeTail > 0) + pathStart = url.indexOf('/', schemeTail + 3); + else + pathStart = url.indexOf('/'); + + if (pathStart > 0) { + String[] tokens = url.substring(pathStart + 1).split("/"); + StringBuilder sb = new StringBuilder(url.substring(0, pathStart)); + for (String token : tokens) { + sb.append("/").append(URLEncoder.encode(token)); + } + + return sb.toString(); + } + + // no need to do URL component encoding + return url; + } + + public static String getCifsUriParametersProblems(URI uri) { + if (!UriUtils.hostAndPathPresent(uri)) { + String errMsg = "cifs URI missing host and/or path. Make sure it's of the format cifs://hostname/path"; + s_logger.warn(errMsg); + return errMsg; + } + return null; + } + + public static boolean hostAndPathPresent(URI uri) { + return !(uri.getHost() == null || uri.getHost().trim().isEmpty() || uri.getPath() == null || uri.getPath().trim().isEmpty()); + } + + public static boolean cifsCredentialsPresent(URI uri) { + List<NameValuePair> args = URLEncodedUtils.parse(uri, "UTF-8"); + boolean foundUser = false; + boolean foundPswd = false; + for (NameValuePair nvp : args) { + String name = nvp.getName(); + if (name.equals("user")) { + foundUser = true; + s_logger.debug("foundUser is" + foundUser); + } else if (name.equals("password")) { + foundPswd = true; + s_logger.debug("foundPswd is" + foundPswd); + } + } + return (foundUser && foundPswd); + } + + public static String getUpdateUri(String url, boolean encrypt) { + String updatedPath = null; + try { + String query = URIUtil.getQuery(url); + URIBuilder builder = new URIBuilder(url); + builder.removeQuery(); + + StringBuilder updatedQuery = new StringBuilder(); + List<NameValuePair> queryParams = getUserDetails(query); + ListIterator<NameValuePair> iterator = queryParams.listIterator(); + while (iterator.hasNext()) { + NameValuePair param = iterator.next(); + String value = null; + if ("password".equalsIgnoreCase(param.getName()) && + param.getValue() != null) { + value = encrypt ? DBEncryptionUtil.encrypt(param.getValue()) : DBEncryptionUtil.decrypt(param.getValue()); + } else { + value = param.getValue(); + } + + if (updatedQuery.length() == 0) { + updatedQuery.append(param.getName()).append('=') + .append(value); + } else { + updatedQuery.append('&').append(param.getName()) + .append('=').append(value); + } + } + + String schemeAndHost = ""; + URI newUri = builder.build(); + if (newUri.getScheme() != null) { + schemeAndHost = newUri.getScheme() + "://" + newUri.getHost(); + } + + updatedPath = schemeAndHost + newUri.getPath() + "?" + updatedQuery; + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Couldn't generate an updated uri. " + e.getMessage()); + } + + return updatedPath; + } + + private static List<NameValuePair> getUserDetails(String query) { + List<NameValuePair> details = new ArrayList<NameValuePair>(); + if (query != null && !query.isEmpty()) { + StringTokenizer allParams = new StringTokenizer(query, "&"); + while (allParams.hasMoreTokens()) { + String param = allParams.nextToken(); + details.add(new BasicNameValuePair(param.substring(0, param.indexOf("=")), + param.substring(param.indexOf("=") + 1))); + } + } + + return details; + } + + // Get the size of a file from URL response header. + public static Long getRemoteSize(String url) { + Long remoteSize = (long)0; + HttpURLConnection httpConn = null; + HttpsURLConnection httpsConn = null; + try { + URI uri = new URI(url); + if (uri.getScheme().equalsIgnoreCase("http")) { + httpConn = (HttpURLConnection)uri.toURL().openConnection(); + if (httpConn != null) { + httpConn.setConnectTimeout(2000); + httpConn.setReadTimeout(5000); + String contentLength = httpConn.getHeaderField("content-length"); + if (contentLength != null) { + remoteSize = Long.parseLong(contentLength); + } + httpConn.disconnect(); + } + } else if (uri.getScheme().equalsIgnoreCase("https")) { + httpsConn = (HttpsURLConnection)uri.toURL().openConnection(); + if (httpsConn != null) { + String contentLength = httpsConn.getHeaderField("content-length"); + if (contentLength != null) { + remoteSize = Long.parseLong(contentLength); + } + httpsConn.disconnect(); + } + } + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid URL " + url); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to establish connection with URL " + url); + } + return remoteSize; + } + + public static Pair<String, Integer> validateUrl(String url) throws IllegalArgumentException { + return validateUrl(null, url); + } + + public static Pair<String, Integer> validateUrl(String format, String url) throws IllegalArgumentException { + try { + URI uri = new URI(url); + if ((uri.getScheme() == null) || + (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file"))) { + throw new IllegalArgumentException("Unsupported scheme for url: " + url); + } + int port = uri.getPort(); + if (!(port == 80 || port == 8080 || port == 443 || port == -1)) { + throw new IllegalArgumentException("Only ports 80, 8080 and 443 are allowed"); + } + + if (port == -1 && uri.getScheme().equalsIgnoreCase("https")) { + port = 443; + } else if (port == -1 && uri.getScheme().equalsIgnoreCase("http")) { + port = 80; + } + + String host = uri.getHost(); + try { + InetAddress hostAddr = InetAddress.getByName(host); + if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress()) { + throw new IllegalArgumentException("Illegal host specified in url"); + } + if (hostAddr instanceof Inet6Address) { + throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")"); + } + } catch (UnknownHostException uhe) { + throw new IllegalArgumentException("Unable to resolve " + host); + } + + // verify format + if (format != null) { + String uripath = uri.getPath(); + checkFormat(format, uripath); + } + return new Pair<String, Integer>(host, port); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + } + + // use http HEAD method to validate url + public static void checkUrlExistence(String url) { + if (url.toLowerCase().startsWith("http") || url.toLowerCase().startsWith("https")) { + HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); + HeadMethod httphead = new HeadMethod(url); + try { + if (httpClient.executeMethod(httphead) != HttpStatus.SC_OK) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + } catch (HttpException hte) { + throw new IllegalArgumentException("Cannot reach URL: " + url); + } catch (IOException ioe) { + throw new IllegalArgumentException("Cannot reach URL: " + url); + } + } + } + + // verify if a URI path is compliance with the file format given + private static void checkFormat(String format, String uripath) { + if ((!uripath.toLowerCase().endsWith("vhd")) && (!uripath.toLowerCase().endsWith("vhd.zip")) && (!uripath.toLowerCase().endsWith("vhd.bz2")) && + (!uripath.toLowerCase().endsWith("vhdx")) && (!uripath.toLowerCase().endsWith("vhdx.gz")) && + (!uripath.toLowerCase().endsWith("vhdx.bz2")) && (!uripath.toLowerCase().endsWith("vhdx.zip")) && + (!uripath.toLowerCase().endsWith("vhd.gz")) && (!uripath.toLowerCase().endsWith("qcow2")) && (!uripath.toLowerCase().endsWith("qcow2.zip")) && + (!uripath.toLowerCase().endsWith("qcow2.bz2")) && (!uripath.toLowerCase().endsWith("qcow2.gz")) && (!uripath.toLowerCase().endsWith("ova")) && + (!uripath.toLowerCase().endsWith("ova.zip")) && (!uripath.toLowerCase().endsWith("ova.bz2")) && (!uripath.toLowerCase().endsWith("ova.gz")) && + (!uripath.toLowerCase().endsWith("tar")) && (!uripath.toLowerCase().endsWith("tar.zip")) && (!uripath.toLowerCase().endsWith("tar.bz2")) && + (!uripath.toLowerCase().endsWith("tar.gz")) && (!uripath.toLowerCase().endsWith("vmdk")) && (!uripath.toLowerCase().endsWith("vmdk.gz")) && + (!uripath.toLowerCase().endsWith("vmdk.zip")) && (!uripath.toLowerCase().endsWith("vmdk.bz2")) && (!uripath.toLowerCase().endsWith("img")) && + (!uripath.toLowerCase().endsWith("img.gz")) && (!uripath.toLowerCase().endsWith("img.zip")) && (!uripath.toLowerCase().endsWith("img.bz2")) && + (!uripath.toLowerCase().endsWith("raw")) && (!uripath.toLowerCase().endsWith("raw.gz")) && (!uripath.toLowerCase().endsWith("raw.bz2")) && + (!uripath.toLowerCase().endsWith("raw.zip")) && (!uripath.toLowerCase().endsWith("iso")) && (!uripath.toLowerCase().endsWith("iso.zip")) + && (!uripath.toLowerCase().endsWith("iso.bz2")) && (!uripath.toLowerCase().endsWith("iso.gz"))) { + throw new IllegalArgumentException("Please specify a valid " + format.toLowerCase()); + } + + if ((format.equalsIgnoreCase("vhd") + && (!uripath.toLowerCase().endsWith("vhd") + && !uripath.toLowerCase().endsWith("vhd.zip") + && !uripath.toLowerCase().endsWith("vhd.bz2") + && !uripath.toLowerCase().endsWith("vhd.gz"))) + || (format.equalsIgnoreCase("vhdx") + && (!uripath.toLowerCase().endsWith("vhdx") + && !uripath.toLowerCase().endsWith("vhdx.zip") + && !uripath.toLowerCase().endsWith("vhdx.bz2") + && !uripath.toLowerCase().endsWith("vhdx.gz"))) + || (format.equalsIgnoreCase("qcow2") + && (!uripath.toLowerCase().endsWith("qcow2") + && !uripath.toLowerCase().endsWith("qcow2.zip") + && !uripath.toLowerCase().endsWith("qcow2.bz2") + && !uripath.toLowerCase().endsWith("qcow2.gz"))) + || (format.equalsIgnoreCase("ova") + && (!uripath.toLowerCase().endsWith("ova") + && !uripath.toLowerCase().endsWith("ova.zip") + && !uripath.toLowerCase().endsWith("ova.bz2") + && !uripath.toLowerCase().endsWith("ova.gz"))) + || (format.equalsIgnoreCase("tar") + && (!uripath.toLowerCase().endsWith("tar") + && !uripath.toLowerCase().endsWith("tar.zip") + && !uripath.toLowerCase().endsWith("tar.bz2") + && !uripath.toLowerCase().endsWith("tar.gz"))) + || (format.equalsIgnoreCase("raw") + && (!uripath.toLowerCase().endsWith("img") + && !uripath.toLowerCase().endsWith("img.zip") + && !uripath.toLowerCase().endsWith("img.bz2") + && !uripath.toLowerCase().endsWith("img.gz") + && !uripath.toLowerCase().endsWith("raw") + && !uripath.toLowerCase().endsWith("raw.bz2") + && !uripath.toLowerCase().endsWith("raw.zip") + && !uripath.toLowerCase().endsWith("raw.gz"))) + || (format.equalsIgnoreCase("vmdk") + && (!uripath.toLowerCase().endsWith("vmdk") + && !uripath.toLowerCase().endsWith("vmdk.zip") + && !uripath.toLowerCase().endsWith("vmdk.bz2") + && !uripath.toLowerCase().endsWith("vmdk.gz"))) + || (format.equalsIgnoreCase("iso") + && (!uripath.toLowerCase().endsWith("iso") + && !uripath.toLowerCase().endsWith("iso.zip") + && !uripath.toLowerCase().endsWith("iso.bz2") + && !uripath.toLowerCase().endsWith("iso.gz")))) { + throw new IllegalArgumentException("Please specify a valid URL. URL:" + uripath + " is an invalid for the format " + format.toLowerCase()); + } + + } + + public static InputStream getInputStreamFromUrl(String url, String user, String password) { + + try { + Pair<String, Integer> hostAndPort = validateUrl(url); + HttpClient httpclient = new HttpClient(new MultiThreadedHttpConnectionManager()); + if ((user != null) && (password != null)) { + httpclient.getParams().setAuthenticationPreemptive(true); + Credentials defaultcreds = new UsernamePasswordCredentials(user, password); + httpclient.getState().setCredentials(new AuthScope(hostAndPort.first(), hostAndPort.second(), AuthScope.ANY_REALM), defaultcreds); + s_logger.info("Added username=" + user + ", password=" + password + "for host " + hostAndPort.first() + ":" + hostAndPort.second()); + } + // Execute the method. + GetMethod method = new GetMethod(url); + int statusCode = httpclient.executeMethod(method); + + if (statusCode != HttpStatus.SC_OK) { + s_logger.error("Failed to read from URL: " + url); + return null; + } + + return method.getResponseBodyAsStream(); + } catch (Exception ex) { + s_logger.error("Failed to read from URL: " + url); + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/UsernamePasswordValidator.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/UsernamePasswordValidator.java b/utils/src/main/java/com/cloud/utils/UsernamePasswordValidator.java new file mode 100644 index 0000000..f11186c --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/UsernamePasswordValidator.java @@ -0,0 +1,49 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UsernamePasswordValidator { + private Pattern usernamePattern; + private Pattern passwordPattern; + private Matcher matcher; + + private static final String USERNAME_PATTERN = "^[a-zA-Z0-9][a-zA-Z0-9@._-]{2,63}$"; + private static final String PASSWORD_PATTERN = "^[a-zA-Z0-9][a-zA-Z0-9@#+=._-]{2,31}$"; + + public UsernamePasswordValidator() { + usernamePattern = Pattern.compile(USERNAME_PATTERN); + passwordPattern = Pattern.compile(PASSWORD_PATTERN); + + } + + public boolean validateUsername(final String username) { + matcher = usernamePattern.matcher(username); + return matcher.matches(); + } + + public boolean validatePassword(final String password) { + matcher = passwordPattern.matcher(password); + return matcher.matches(); + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/UuidUtils.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/UuidUtils.java b/utils/src/main/java/com/cloud/utils/UuidUtils.java new file mode 100644 index 0000000..9c4a756 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/UuidUtils.java @@ -0,0 +1,34 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils; + +import org.apache.xerces.impl.xpath.regex.RegularExpression; + +public class UuidUtils { + + public final static String first(String uuid) { + return uuid.substring(0, uuid.indexOf('-')); + } + + public static boolean validateUUID(String uuid) { + RegularExpression regex = new RegularExpression("[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"); + return regex.matches(uuid); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/backoff/BackoffAlgorithm.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/backoff/BackoffAlgorithm.java b/utils/src/main/java/com/cloud/utils/backoff/BackoffAlgorithm.java new file mode 100644 index 0000000..b8f3f00 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/backoff/BackoffAlgorithm.java @@ -0,0 +1,38 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils.backoff; + +import com.cloud.utils.component.Adapter; + +/** + * BackoffAlgorithm implements multiple BackoffAlgorithm. + */ +public interface BackoffAlgorithm extends Adapter { + + /** + */ + void waitBeforeRetry(); + + /** + * no longer need to backoff. reset to beginning. + */ + void reset(); + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoff.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoff.java b/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoff.java new file mode 100644 index 0000000..14eae16 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoff.java @@ -0,0 +1,102 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils.backoff.impl; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ejb.Local; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.backoff.BackoffAlgorithm; +import com.cloud.utils.component.AdapterBase; + +/** + * An implementation of BackoffAlgorithm that waits for some seconds. + * After the time the client can try to perform the operation again. + * + * @config + * {@table + * || Param Name | Description | Values | Default || + * || seconds | seconds to sleep | integer | 5 || + * } + **/ +@Local(value = {BackoffAlgorithm.class}) +public class ConstantTimeBackoff extends AdapterBase implements BackoffAlgorithm, ConstantTimeBackoffMBean { + long _time; + private final Map<String, Thread> _asleep = new ConcurrentHashMap<String, Thread>(); + private final static Log LOG = LogFactory.getLog(ConstantTimeBackoff.class); + + @Override + public void waitBeforeRetry() { + Thread current = Thread.currentThread(); + try { + _asleep.put(current.getName(), current); + Thread.sleep(_time); + } catch (InterruptedException e) { + // JMX or other threads may interrupt this thread, but let's log it + // anyway, no exception to log as this is not an error + LOG.info("Thread " + current.getName() + " interrupted while waiting for retry"); + } finally { + _asleep.remove(current.getName()); + } + return; + } + + @Override + public void reset() { + } + + @Override + public boolean configure(String name, Map<String, Object> params) { + _time = NumbersUtil.parseLong((String)params.get("seconds"), 5) * 1000; + return true; + } + + @Override + public Collection<String> getWaiters() { + return _asleep.keySet(); + } + + @Override + public boolean wakeup(String threadName) { + Thread th = _asleep.get(threadName); + if (th != null) { + th.interrupt(); + return true; + } + + return false; + } + + @Override + public long getTimeToWait() { + return _time; + } + + @Override + public void setTimeToWait(long seconds) { + _time = seconds * 1000; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffMBean.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffMBean.java b/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffMBean.java new file mode 100644 index 0000000..4dc9f9c --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffMBean.java @@ -0,0 +1,35 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils.backoff.impl; + +import java.util.Collection; + +import com.cloud.utils.mgmt.ManagementBean; + +public interface ConstantTimeBackoffMBean extends ManagementBean { + public long getTimeToWait(); + + public void setTimeToWait(long seconds); + + Collection<String> getWaiters(); + + boolean wakeup(String threadName); + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/NetconfHelper.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/NetconfHelper.java b/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/NetconfHelper.java new file mode 100644 index 0000000..a6d905d --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/NetconfHelper.java @@ -0,0 +1,355 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils.cisco.n1kv.vsm; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.trilead.ssh2.Connection; +import com.trilead.ssh2.Session; + +import com.cloud.utils.Pair; +import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.BindingType; +import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.PortProfileType; +import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.SwitchPortMode; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.ssh.SSHCmdHelper; + +public class NetconfHelper { + private static final Logger s_logger = Logger.getLogger(NetconfHelper.class); + + private static final String SSH_NETCONF_TERMINATOR = "]]>]]>"; + + private Connection _connection; + + private Session _session; + + public NetconfHelper(String ip, String username, String password) throws CloudRuntimeException { + _connection = SSHCmdHelper.acquireAuthorizedConnection(ip, username, password); + if (_connection == null) { + throw new CloudRuntimeException("Error opening ssh connection."); + } + + try { + _session = _connection.openSession(); + _session.startSubSystem("xmlagent"); + exchangeHello(); + } catch (final Exception e) { + disconnect(); + s_logger.error("Failed to connect to device SSH server: " + e.getMessage()); + throw new CloudRuntimeException("Failed to connect to SSH server: " + _connection.getHostname()); + } + } + + public void disconnect() { + if (_session != null) { + _session.close(); + } + SSHCmdHelper.releaseSshConnection(_connection); + } + + public void queryStatus() throws CloudRuntimeException { + // This command is used to query the server status. + String status = + "<?xml version=\"1.0\"?>" + "<nc:rpc message-id=\"1\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0" + "\"xmlns=\"http://www.cisco.com/nxos:1.0:xml\">" + + " <nc:get>" + " <nc:filter type=\"subtree\">" + " <show>" + " <xml>" + " <server>" + " <status/>" + + " </server>" + " </xml>" + " </show>" + " </nc:filter>" + " </nc:get>" + "</nc:rpc>" + SSH_NETCONF_TERMINATOR; + send(status); + // parse the rpc reply. + parseOkReply(receive()); + } + + public void addPortProfile(String name, PortProfileType type, BindingType binding, SwitchPortMode mode, int vlanid, String vdc, String espName) + throws CloudRuntimeException { + String command = VsmCommand.getAddPortProfile(name, type, binding, mode, vlanid, vdc, espName); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for adding port profile."); + } + } + + public void addPortProfile(String name, PortProfileType type, BindingType binding, SwitchPortMode mode, int vlanid) throws CloudRuntimeException { + String command = VsmCommand.getAddPortProfile(name, type, binding, mode, vlanid); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for adding port profile."); + } + } + + public void updatePortProfile(String name, SwitchPortMode mode, List<Pair<VsmCommand.OperationType, String>> params) throws CloudRuntimeException { + String command = VsmCommand.getUpdatePortProfile(name, mode, params); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for updating port profile."); + } + } + + public void deletePortProfile(String name) throws CloudRuntimeException { + String command = VsmCommand.getDeletePortProfile(name); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for deleting port profile."); + } + } + + public void addPolicyMap(String name, int averageRate, int maxRate, int burstRate) throws CloudRuntimeException { + String command = VsmCommand.getAddPolicyMap(name, averageRate, maxRate, burstRate); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for adding/updating policy map."); + } + } + + public void deletePolicyMap(String name) throws CloudRuntimeException { + String command = VsmCommand.getDeletePolicyMap(name); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for deleting policy map."); + } + } + + public void updatePolicyMap(String name, int averageRate, int maxRate, int burstRate) throws CloudRuntimeException { + // Add and update of policy map work in the exact same way. + addPolicyMap(name, averageRate, maxRate, burstRate); + } + + public void attachServicePolicy(String policyMap, String portProfile) throws CloudRuntimeException { + String command = VsmCommand.getServicePolicy(policyMap, portProfile, true); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for adding policy map."); + } + } + + public void detachServicePolicy(String policyMap, String portProfile) throws CloudRuntimeException { + String command = VsmCommand.getServicePolicy(policyMap, portProfile, false); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for removing policy map."); + } + } + + public void addVServiceNode(String vlanId, String ipAddr) throws CloudRuntimeException { + String command = VsmCommand.getVServiceNode(vlanId, ipAddr); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + parseOkReply(sendAndReceive(command)); + } else { + throw new CloudRuntimeException("Error generating rpc request for adding vservice node for vlan " + vlanId); + } + } + + public PortProfile getPortProfileByName(String name) throws CloudRuntimeException { + String command = VsmCommand.getPortProfile(name); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + String received = sendAndReceive(command); + VsmPortProfileResponse response = new VsmPortProfileResponse(received.trim()); + if (!response.isResponseOk()) { + throw new CloudRuntimeException(response.toString()); + } else { + return response.getPortProfile(); + } + } else { + throw new CloudRuntimeException("Error generating rpc request for getting port profile."); + } + } + + public PolicyMap getPolicyMapByName(String name) throws CloudRuntimeException { + String command = VsmCommand.getPolicyMap(name); + if (command != null) { + command = command.concat(SSH_NETCONF_TERMINATOR); + String received = sendAndReceive(command); + VsmPolicyMapResponse response = new VsmPolicyMapResponse(received.trim()); + if (!response.isResponseOk()) { + throw new CloudRuntimeException(response.toString()); + } else { + return response.getPolicyMap(); + } + } else { + throw new CloudRuntimeException("Error generating rpc request for getting policy map."); + } + } + + private void exchangeHello() { + String ack = receive(); + String hello = VsmCommand.getHello() + SSH_NETCONF_TERMINATOR; + send(hello); + } + + private String sendAndReceive(String command) { + String received; + synchronized (NetconfHelper.class) { + send(command); + received = receive(); + } + return received; + } + + private void send(String message) { + try { + OutputStream outputStream = _session.getStdin(); + outputStream.write(message.getBytes()); + outputStream.flush(); + } catch (Exception e) { + s_logger.error("Failed to send message: " + e.getMessage()); + throw new CloudRuntimeException("Failed to send message: " + e.getMessage()); + } + } + + private String receive() { + String response = new String(""); + InputStream inputStream = _session.getStdout(); + + try { + Delimiter delimiter = new Delimiter(); + byte[] buffer = new byte[1024]; + int count = 0; + + // Read the input stream till we find the end sequence ']]>]]>'. + while (true) { + int data = inputStream.read(); + if (data != -1) { + byte[] dataStream = delimiter.parse(data); + if (delimiter.endReached()) { + response += new String(buffer, 0, count); + break; + } + + if (dataStream != null) { + for (int i = 0; i < dataStream.length; i++) { + buffer[count] = dataStream[i]; + count++; + if (count == 1024) { + response += new String(buffer, 0, count); + count = 0; + } + } + } + } else { + break; + } + } + } catch (final Exception e) { + throw new CloudRuntimeException("Error occured while reading from the stream: " + e.getMessage()); + } + + return response; + } + + private void parseOkReply(String reply) throws CloudRuntimeException { + VsmOkResponse response = new VsmOkResponse(reply.trim()); + if (!response.isResponseOk()) { + throw new CloudRuntimeException(response.toString()); + } + } + + private static class Delimiter { + private boolean _endReached = false; + + // Used to accumulate response read while searching for end of response. + private byte[] _gatherResponse = new byte[6]; + + // Index into number of bytes read. + private int _offset = 0; + + // True if ']]>]]>' detected. + boolean endReached() { + return _endReached; + } + + // Parses the input stream and checks if end sequence is reached. + byte[] parse(int input) throws RuntimeException { + boolean collect = false; + byte[] streamRead = null; + + // Check if end sequence matched. + switch (_offset) { + case 0: + if (input == ']') { + collect = true; + } + break; + case 1: + if (input == ']') { + collect = true; + } + break; + case 2: + if (input == '>') { + collect = true; + } + break; + case 3: + if (input == ']') { + collect = true; + } + break; + case 4: + if (input == ']') { + collect = true; + } + break; + case 5: + if (input == '>') { + collect = true; + _endReached = true; + } + break; + default: + throw new RuntimeException("Invalid index value: " + _offset); + } + + if (collect) { + _gatherResponse[_offset++] = (byte)input; + } else { + // End sequence not yet reached. Return the stream of bytes collected so far. + streamRead = new byte[_offset + 1]; + for (int index = 0; index < _offset; ++index) { + streamRead[index] = _gatherResponse[index]; + } + + streamRead[_offset] = (byte)input; + _offset = 0; + } + + return streamRead; + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PolicyMap.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PolicyMap.java b/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PolicyMap.java new file mode 100644 index 0000000..01605d7 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PolicyMap.java @@ -0,0 +1,34 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils.cisco.n1kv.vsm; + +public class PolicyMap { + public String policyMapName; + public int committedRate; + public int burstRate; + public int peakRate; + + PolicyMap() { + policyMapName = null; + committedRate = 0; + burstRate = 0; + peakRate = 0; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PortProfile.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PortProfile.java b/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PortProfile.java new file mode 100644 index 0000000..53d9728 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PortProfile.java @@ -0,0 +1,48 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.utils.cisco.n1kv.vsm; + +import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.BindingType; +import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.PortProfileType; +import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.SwitchPortMode; + +public class PortProfile { + public PortProfileType type; + public SwitchPortMode mode; + public BindingType binding; + public String profileName; + public String inputPolicyMap; + public String outputPolicyMap; + public String vlan; + public boolean status; + public int maxPorts; + + PortProfile() { + profileName = null; + inputPolicyMap = null; + outputPolicyMap = null; + vlan = null; + status = false; + maxPorts = 32; + type = PortProfileType.none; + mode = SwitchPortMode.none; + binding = BindingType.none; + } +}