|
@@ -18,169 +18,19 @@ limitations under the License.
|
|
|
"""
|
|
|
|
|
|
import re
|
|
|
-import socket
|
|
|
import sys
|
|
|
|
|
|
-from stack_advisor import StackAdvisor
|
|
|
+from stack_advisor import DefaultStackAdvisor
|
|
|
|
|
|
-class HDP132StackAdvisor(StackAdvisor):
|
|
|
+class HDP132StackAdvisor(DefaultStackAdvisor):
|
|
|
|
|
|
- def recommendComponentLayout(self, services, hosts):
|
|
|
- """
|
|
|
- Returns Services object with hostnames array populated for components
|
|
|
- If hostnames are populated for some components (partial blueprint) - these components will not be processed
|
|
|
- """
|
|
|
- stackName = services["Versions"]["stack_name"]
|
|
|
- stackVersion = services["Versions"]["stack_version"]
|
|
|
- hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
|
|
|
- servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
|
|
|
-
|
|
|
- recommendations = {
|
|
|
- "Versions": {"stack_name": stackName, "stack_version": stackVersion},
|
|
|
- "hosts": hostsList,
|
|
|
- "services": servicesList,
|
|
|
- "recommendations": {
|
|
|
- "blueprint": {
|
|
|
- "host_groups": [ ]
|
|
|
- },
|
|
|
- "blueprint_cluster_binding": {
|
|
|
- "host_groups": [ ]
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- hostsComponentsMap = {}
|
|
|
-
|
|
|
- #extend 'hostsComponentsMap' with MASTER components
|
|
|
- for service in services["services"]:
|
|
|
- masterComponents = [component for component in service["components"] if isMaster(component)]
|
|
|
- for component in masterComponents:
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- hostsForComponent = []
|
|
|
-
|
|
|
- if isAlreadyPopulated(component):
|
|
|
- hostsForComponent = component["StackServiceComponents"]["hostnames"]
|
|
|
- else:
|
|
|
- availableHosts = hostsList
|
|
|
- if len(hostsList) > 1 and self.isNotPreferableOnAmbariServerHost(component):
|
|
|
- availableHosts = [hostName for hostName in hostsList if not isLocalHost(hostName)]
|
|
|
-
|
|
|
- if isMasterWithMultipleInstances(component):
|
|
|
- hostsCount = defaultNoOfMasterHosts(component)
|
|
|
- if hostsCount > 1: # get first 'hostsCount' available hosts
|
|
|
- if len(availableHosts) < hostsCount:
|
|
|
- hostsCount = len(availableHosts)
|
|
|
- hostsForComponent = availableHosts[:hostsCount]
|
|
|
- else:
|
|
|
- hostsForComponent = [self.getHostForComponent(component, availableHosts)]
|
|
|
- else:
|
|
|
- hostsForComponent = [self.getHostForComponent(component, availableHosts)]
|
|
|
-
|
|
|
- #extend 'hostsComponentsMap' with 'hostsForComponent'
|
|
|
- for hostName in hostsForComponent:
|
|
|
- if hostName not in hostsComponentsMap:
|
|
|
- hostsComponentsMap[hostName] = []
|
|
|
- hostsComponentsMap[hostName].append( { "name":componentName } )
|
|
|
-
|
|
|
- #extend 'hostsComponentsMap' with Slave and Client Components
|
|
|
- componentsListList = [service["components"] for service in services["services"]]
|
|
|
- componentsList = [item for sublist in componentsListList for item in sublist]
|
|
|
- usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not self.isNotValuable(component)]
|
|
|
- utilizedHosts = [item for sublist in usedHostsListList for item in sublist]
|
|
|
- freeHosts = [hostName for hostName in hostsList if hostName not in utilizedHosts]
|
|
|
-
|
|
|
- for service in services["services"]:
|
|
|
- slaveClientComponents = [component for component in service["components"] if isSlave(component) or isClient(component)]
|
|
|
- for component in slaveClientComponents:
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- hostsForComponent = []
|
|
|
-
|
|
|
- if isAlreadyPopulated(component):
|
|
|
- hostsForComponent = component["StackServiceComponents"]["hostnames"]
|
|
|
- elif component["StackServiceComponents"]["cardinality"] == "ALL":
|
|
|
- hostsForComponent = hostsList
|
|
|
- else:
|
|
|
- if len(freeHosts) == 0:
|
|
|
- hostsForComponent = hostsList[-1:]
|
|
|
- else: # len(freeHosts) >= 1
|
|
|
- hostsForComponent = freeHosts
|
|
|
- if isClient(component):
|
|
|
- hostsForComponent = freeHosts[0:1]
|
|
|
-
|
|
|
- #extend 'hostsComponentsMap' with 'hostsForComponent'
|
|
|
- for hostName in hostsForComponent:
|
|
|
- if hostName not in hostsComponentsMap:
|
|
|
- hostsComponentsMap[hostName] = []
|
|
|
- hostsComponentsMap[hostName].append( { "name": componentName } )
|
|
|
-
|
|
|
- #prepare 'host-group's from 'hostsComponentsMap'
|
|
|
- host_groups = recommendations["recommendations"]["blueprint"]["host_groups"]
|
|
|
- bindings = recommendations["recommendations"]["blueprint_cluster_binding"]["host_groups"]
|
|
|
- index = 0
|
|
|
- for key in hostsComponentsMap.keys():
|
|
|
- index += 1
|
|
|
- host_group_name = "host-group-{0}".format(index)
|
|
|
- host_groups.append( { "name": host_group_name, "components": hostsComponentsMap[key] } )
|
|
|
- bindings.append( { "name": host_group_name, "hosts": [{ "fqdn": socket.getfqdn(key) }] } )
|
|
|
-
|
|
|
- return recommendations
|
|
|
- pass
|
|
|
-
|
|
|
- def getHostForComponent(self, component, hostsList):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- scheme = self.defineSelectionScheme(componentName)
|
|
|
-
|
|
|
- if len(hostsList) == 1:
|
|
|
- return hostsList[0]
|
|
|
- else:
|
|
|
- for key in scheme.keys():
|
|
|
- if isinstance(key, ( int, long )):
|
|
|
- if len(hostsList) < key:
|
|
|
- return hostsList[scheme[key]]
|
|
|
- return hostsList[scheme['else']]
|
|
|
-
|
|
|
- def defineSelectionScheme(self, componentName):
|
|
|
- scheme = self.selectionScheme(componentName)
|
|
|
- if scheme is None:
|
|
|
- scheme = {"else": 0}
|
|
|
- return scheme
|
|
|
-
|
|
|
- def selectionScheme(self, componentName):
|
|
|
- return {
|
|
|
- 'NAMENODE': {"else": 0},
|
|
|
- 'SECONDARY_NAMENODE': {"else": 1},
|
|
|
- 'HBASE_MASTER': {6: 0, 31: 2, "else": 3},
|
|
|
-
|
|
|
- 'HISTORYSERVER': {31: 1, "else": 2},
|
|
|
- 'RESOURCEMANAGER': {31: 1, "else": 2},
|
|
|
-
|
|
|
- 'OOZIE_SERVER': {6: 1, 31: 2, "else": 3},
|
|
|
-
|
|
|
- 'HIVE_SERVER': {6: 1, 31: 2, "else": 4},
|
|
|
- 'HIVE_METASTORE': {6: 1, 31: 2, "else": 4},
|
|
|
- 'WEBHCAT_SERVER': {6: 1, 31: 2, "else": 4},
|
|
|
- }.get(componentName, None)
|
|
|
-
|
|
|
- def isNotPreferableOnAmbariServerHost(self, component):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- service = ['GANGLIA_SERVER', 'NAGIOS_SERVER']
|
|
|
- return componentName in service
|
|
|
-
|
|
|
- def validateComponentLayout(self, services, hosts):
|
|
|
+ def getLayoutValidationItems(self, services, hosts):
|
|
|
"""Returns array of Validation objects about issues with hostnames components assigned to"""
|
|
|
- stackName = services["Versions"]["stack_name"]
|
|
|
- stackVersion = services["Versions"]["stack_version"]
|
|
|
-
|
|
|
- validations = {
|
|
|
- "Versions": {"stack_name": stackName, "stack_version": stackVersion},
|
|
|
- "items": [ ]
|
|
|
- }
|
|
|
- items = validations["items"]
|
|
|
+ items = []
|
|
|
|
|
|
# Validating NAMENODE and SECONDARY_NAMENODE are on different hosts if possible
|
|
|
hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
|
|
|
hostsCount = len(hostsList)
|
|
|
- servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
|
|
|
|
|
|
componentsListList = [service["components"] for service in services["services"]]
|
|
|
componentsList = [item for sublist in componentsListList for item in sublist]
|
|
@@ -198,32 +48,28 @@ class HDP132StackAdvisor(StackAdvisor):
|
|
|
# Validating cardinality
|
|
|
for component in componentsList:
|
|
|
if component["StackServiceComponents"]["cardinality"] is not None:
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- componentHostsCount = 0
|
|
|
- if component["StackServiceComponents"]["hostnames"] is not None:
|
|
|
- componentHostsCount = len(component["StackServiceComponents"]["hostnames"])
|
|
|
- cardinality = str(component["StackServiceComponents"]["cardinality"])
|
|
|
- # cardinality types: null, 1+, 1-2, 1, ALL
|
|
|
- hostsMax = -sys.maxint - 1
|
|
|
- hostsMin = sys.maxint
|
|
|
- hostsMin = 0
|
|
|
- hostsMax = 0
|
|
|
- if "+" in cardinality:
|
|
|
- hostsMin = int(cardinality[:-1])
|
|
|
- hostsMax = sys.maxint
|
|
|
- elif "-" in cardinality:
|
|
|
- nums = cardinality.split("-")
|
|
|
- hostsMin = int(nums[0])
|
|
|
- hostsMax = int(nums[1])
|
|
|
- elif "ALL" == cardinality:
|
|
|
- hostsMin = hostsCount
|
|
|
- hostsMax = hostsCount
|
|
|
- else:
|
|
|
- hostsMin = int(cardinality)
|
|
|
- hostsMax = int(cardinality)
|
|
|
-
|
|
|
- if componentHostsCount > hostsMax or componentHostsCount < hostsMin:
|
|
|
- items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Cardinality violation, cardinality={0}, hosts count={1}'.format(cardinality, str(componentHostsCount)), "component-name": str(componentName) } )
|
|
|
+ componentName = component["StackServiceComponents"]["component_name"]
|
|
|
+ componentHostsCount = 0
|
|
|
+ if component["StackServiceComponents"]["hostnames"] is not None:
|
|
|
+ componentHostsCount = len(component["StackServiceComponents"]["hostnames"])
|
|
|
+ cardinality = str(component["StackServiceComponents"]["cardinality"])
|
|
|
+ # cardinality types: null, 1+, 1-2, 1, ALL
|
|
|
+ if "+" in cardinality:
|
|
|
+ hostsMin = int(cardinality[:-1])
|
|
|
+ hostsMax = sys.maxint
|
|
|
+ elif "-" in cardinality:
|
|
|
+ nums = cardinality.split("-")
|
|
|
+ hostsMin = int(nums[0])
|
|
|
+ hostsMax = int(nums[1])
|
|
|
+ elif "ALL" == cardinality:
|
|
|
+ hostsMin = hostsCount
|
|
|
+ hostsMax = hostsCount
|
|
|
+ else:
|
|
|
+ hostsMin = int(cardinality)
|
|
|
+ hostsMax = int(cardinality)
|
|
|
+
|
|
|
+ if componentHostsCount > hostsMax or componentHostsCount < hostsMin:
|
|
|
+ items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Cardinality violation, cardinality={0}, hosts count={1}'.format(cardinality, str(componentHostsCount)), "component-name": str(componentName) } )
|
|
|
|
|
|
# Validating host-usage
|
|
|
usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not self.isNotValuable(component)]
|
|
@@ -232,52 +78,11 @@ class HDP132StackAdvisor(StackAdvisor):
|
|
|
for host in nonUsedHostsList:
|
|
|
items.append( { "type": 'host-component', "level": 'ERROR', "message": 'Host is not used', "host": str(host) } )
|
|
|
|
|
|
- return validations
|
|
|
- pass
|
|
|
-
|
|
|
- def isNotValuable(self, component):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- service = ['JOURNALNODE', 'ZKFC', 'GANGLIA_MONITOR']
|
|
|
- return componentName in service
|
|
|
-
|
|
|
- def recommendConfigurations(self, services, hosts):
|
|
|
- stackName = services["Versions"]["stack_name"]
|
|
|
- stackVersion = services["Versions"]["stack_version"]
|
|
|
- hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
|
|
|
- servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
|
|
|
- components = [component["StackServiceComponents"]["component_name"]
|
|
|
- for service in services["services"]
|
|
|
- for component in service["components"]]
|
|
|
-
|
|
|
- clusterData = self.getClusterData(servicesList, hosts, components)
|
|
|
-
|
|
|
- recommendations = {
|
|
|
- "Versions": {"stack_name": stackName, "stack_version": stackVersion},
|
|
|
- "hosts": hostsList,
|
|
|
- "services": servicesList,
|
|
|
- "recommendations": {
|
|
|
- "blueprint": {
|
|
|
- "configurations": {},
|
|
|
- "host_groups": []
|
|
|
- },
|
|
|
- "blueprint_cluster_binding": {
|
|
|
- "host_groups": []
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ return items
|
|
|
|
|
|
- configurations = recommendations["recommendations"]["blueprint"]["configurations"]
|
|
|
-
|
|
|
- for service in servicesList:
|
|
|
- calculation = self.recommendServiceConfigurations(service)
|
|
|
- if calculation is not None:
|
|
|
- calculation(configurations, clusterData)
|
|
|
-
|
|
|
- return recommendations
|
|
|
-
|
|
|
- def recommendServiceConfigurations(self, service):
|
|
|
+ def getServiceConfiguratorDict(self):
|
|
|
return {
|
|
|
- }.get(service, None)
|
|
|
+ }
|
|
|
|
|
|
def putProperty(self, config, configType):
|
|
|
config[configType] = {"properties": {}}
|
|
@@ -361,17 +166,9 @@ class HDP132StackAdvisor(StackAdvisor):
|
|
|
|
|
|
return cluster
|
|
|
|
|
|
-
|
|
|
- def validateConfigurations(self, services, hosts):
|
|
|
+ def getConfigurationsValidationItems(self, services, hosts):
|
|
|
"""Returns array of Validation objects about issues with configuration values provided in services"""
|
|
|
- stackName = services["Versions"]["stack_name"]
|
|
|
- stackVersion = services["Versions"]["stack_version"]
|
|
|
-
|
|
|
- validations = {
|
|
|
- "Versions": {"stack_name": stackName, "stack_version": stackVersion},
|
|
|
- "items": [ ]
|
|
|
- }
|
|
|
- items = validations["items"]
|
|
|
+ items = []
|
|
|
|
|
|
recommendations = self.recommendConfigurations(services, hosts)
|
|
|
recommendedDefaults = recommendations["recommendations"]["blueprint"]["configurations"]
|
|
@@ -388,50 +185,88 @@ class HDP132StackAdvisor(StackAdvisor):
|
|
|
if siteProperties is not None:
|
|
|
resultItems = method(siteProperties, recommendedDefaults[siteName]["properties"])
|
|
|
items.extend(resultItems)
|
|
|
- return validations
|
|
|
- pass
|
|
|
+ return items
|
|
|
+
|
|
|
+ def getServiceConfigurationValidators(self):
|
|
|
+ return {}
|
|
|
|
|
|
def validateServiceConfigurations(self, serviceName):
|
|
|
- return {
|
|
|
- }.get(serviceName, None)
|
|
|
+ return self.getServiceConfigurationValidators().get(serviceName, None)
|
|
|
|
|
|
- def toConfigurationValidationErrors(self, items, siteName):
|
|
|
+ def toConfigurationValidationProblems(self, validationProblems, siteName):
|
|
|
result = []
|
|
|
- for item in items:
|
|
|
- if item["message"] is not None:
|
|
|
- error = { "type": 'configuration', "level": 'ERROR', "message": item["message"], "config-type": siteName, "config-name": item["config-name"] }
|
|
|
- result.append(error)
|
|
|
+ for validationProblem in validationProblems:
|
|
|
+ validationItem = validationProblem.get("item", None)
|
|
|
+ if validationItem is not None:
|
|
|
+ problem = {"type": 'configuration', "level": validationItem["level"], "message": validationItem["message"],
|
|
|
+ "config-type": siteName, "config-name": validationProblem["config-name"] }
|
|
|
+ result.append(problem)
|
|
|
return result
|
|
|
|
|
|
+ def getWarnItem(self, message):
|
|
|
+ return {"level": "WARN", "message": message}
|
|
|
+
|
|
|
+ def getErrorItem(self, message):
|
|
|
+ return {"level": "ERROR", "message": message}
|
|
|
+
|
|
|
def validatorLessThenDefaultValue(self, properties, recommendedDefaults, propertyName):
|
|
|
if not propertyName in properties:
|
|
|
- return "Value should be set"
|
|
|
+ return self.getErrorItem("Value should be set")
|
|
|
value = to_number(properties[propertyName])
|
|
|
if value is None:
|
|
|
- return "Value should be integer"
|
|
|
+ return self.getErrorItem("Value should be integer")
|
|
|
defaultValue = to_number(recommendedDefaults[propertyName])
|
|
|
if defaultValue is None:
|
|
|
return None
|
|
|
if value < defaultValue:
|
|
|
- return "Value is less than the recommended default of {0}".format(defaultValue)
|
|
|
+ return self.getWarnItem("Value is less than the recommended default of {0}".format(defaultValue))
|
|
|
return None
|
|
|
|
|
|
def validateXmxValue(self, properties, recommendedDefaults, propertyName):
|
|
|
if not propertyName in properties:
|
|
|
- return "Value should be set"
|
|
|
+ return self.getErrorItem("Value should be set")
|
|
|
value = properties[propertyName]
|
|
|
defaultValue = recommendedDefaults[propertyName]
|
|
|
if defaultValue is None:
|
|
|
- return "Config's default value can't be null or undefined"
|
|
|
+ return self.getErrorItem("Config's default value can't be null or undefined")
|
|
|
if not checkXmxValueFormat(value):
|
|
|
- return 'Invalid value format'
|
|
|
+ return self.getErrorItem('Invalid value format')
|
|
|
valueInt = formatXmxSizeToBytes(getXmxSize(value))
|
|
|
defaultValueXmx = getXmxSize(defaultValue)
|
|
|
defaultValueInt = formatXmxSizeToBytes(defaultValueXmx)
|
|
|
if valueInt < defaultValueInt:
|
|
|
- return "Value is less than the recommended default of -Xmx" + defaultValueXmx
|
|
|
+ return self.getWarnItem("Value is less than the recommended default of -Xmx" + defaultValueXmx)
|
|
|
return None
|
|
|
+ def getMastersWithMultipleInstances(self):
|
|
|
+ return ['ZOOKEEPER_SERVER', 'HBASE_MASTER']
|
|
|
|
|
|
+ def getNotValuableComponents(self):
|
|
|
+ return ['JOURNALNODE', 'ZKFC', 'GANGLIA_MONITOR']
|
|
|
+
|
|
|
+ def getNotPreferableOnServerComponents(self):
|
|
|
+ return ['GANGLIA_SERVER', 'NAGIOS_SERVER']
|
|
|
+
|
|
|
+ def getCardinalitiesDict(self):
|
|
|
+ return {
|
|
|
+ 'ZOOKEEPER_SERVER': {"min": 3},
|
|
|
+ 'HBASE_MASTER': {"min": 1},
|
|
|
+ }
|
|
|
+
|
|
|
+ def selectionSchemes(self):
|
|
|
+ return {
|
|
|
+ 'NAMENODE': {"else": 0},
|
|
|
+ 'SECONDARY_NAMENODE': {"else": 1},
|
|
|
+ 'HBASE_MASTER': {6: 0, 31: 2, "else": 3},
|
|
|
+
|
|
|
+ 'HISTORYSERVER': {31: 1, "else": 2},
|
|
|
+ 'RESOURCEMANAGER': {31: 1, "else": 2},
|
|
|
+
|
|
|
+ 'OOZIE_SERVER': {6: 1, 31: 2, "else": 3},
|
|
|
+
|
|
|
+ 'HIVE_SERVER': {6: 1, 31: 2, "else": 4},
|
|
|
+ 'HIVE_METASTORE': {6: 1, 31: 2, "else": 4},
|
|
|
+ 'WEBHCAT_SERVER': {6: 1, 31: 2, "else": 4},
|
|
|
+ }
|
|
|
|
|
|
# Validation helper methods
|
|
|
def getSiteProperties(configurations, siteName):
|
|
@@ -474,46 +309,6 @@ def formatXmxSizeToBytes(value):
|
|
|
modifier == 'g': 1024 * 1024 * 1024,
|
|
|
modifier == 't': 1024 * 1024 * 1024 * 1024,
|
|
|
modifier == 'p': 1024 * 1024 * 1024 * 1024 * 1024
|
|
|
- }[1]
|
|
|
+ }[1]
|
|
|
return to_number(value) * m
|
|
|
|
|
|
-
|
|
|
-# Recommendation helper methods
|
|
|
-def isAlreadyPopulated(component):
|
|
|
- if component["StackServiceComponents"]["hostnames"] is not None:
|
|
|
- return len(component["StackServiceComponents"]["hostnames"]) > 0
|
|
|
- return False
|
|
|
-
|
|
|
-def isClient(component):
|
|
|
- return component["StackServiceComponents"]["component_category"] == 'CLIENT'
|
|
|
-
|
|
|
-def isSlave(component):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- isSlave = component["StackServiceComponents"]["component_category"] == 'SLAVE'
|
|
|
- return isSlave
|
|
|
-
|
|
|
-def isMaster(component):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- isMaster = component["StackServiceComponents"]["is_master"]
|
|
|
- return isMaster
|
|
|
-
|
|
|
-def isLocalHost(hostName):
|
|
|
- return socket.getfqdn(hostName) == socket.getfqdn()
|
|
|
-
|
|
|
-def isMasterWithMultipleInstances(component):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- masters = ['ZOOKEEPER_SERVER', 'HBASE_MASTER']
|
|
|
- return componentName in masters
|
|
|
-
|
|
|
-def defaultNoOfMasterHosts(component):
|
|
|
- componentName = component["StackServiceComponents"]["component_name"]
|
|
|
- return cardinality(componentName)[min]
|
|
|
-
|
|
|
-
|
|
|
-# Helper dictionaries
|
|
|
-def cardinality(componentName):
|
|
|
- return {
|
|
|
- 'ZOOKEEPER_SERVER': {min: 3},
|
|
|
- 'HBASE_MASTER': {min: 1},
|
|
|
- }.get(componentName, {min:1, max:1})
|
|
|
-
|