Beispiel #1
0
func (self *Huddle) ListInstances(tenant string, bundleType bundletype.BundleType) ([]*Instance, error) {
	prefix := self.jujuPrefix(tenant, bundleType)

	statuses, err := self.JujuClient.GetServiceStatusList(prefix)
	if err != nil {
		return nil, err
	}
	if statuses == nil {
		return nil, rs.HttpError(http.StatusNotFound)
	}

	instances := []*Instance{}
	for key, state := range statuses {
		_, bundleTypeId, instanceId, module, _, err := ParseUnit(key)
		if err != nil {
			log.Debug("Ignoring unparseable service: %v", key)
			continue
		}

		assert.That(bundleTypeId == bundleType.Key())

		if module != bundleType.PrimaryJujuService() {
			continue
		}

		i := self.NewInstance(tenant, bundleType, instanceId)
		i.cacheState(&state)

		instances = append(instances, i)
	}

	return instances, nil
}
Beispiel #2
0
func (self *JsonMessageBodyReader) Read(t reflect.Type, req *http.Request, mediaType *MediaType) (interface{}, error) {
	body := req.Body
	defer body.Close()

	decoder := json.NewDecoder(req.Body)

	valueT := t
	pointerDepth := 0
	for valueT.Kind() == reflect.Ptr {
		valueT = valueT.Elem()
		pointerDepth++
	}

	msg := reflect.New(valueT)

	err := decoder.Decode(msg.Interface())
	if err != nil { // && err != io.EOF {
		return nil, err
	}

	//log.Debug("PointerDepth: %v, type %v", pointerDepth, t)

	// reflect.New returns a pointer, so we start at 1
	for i := 1; i < pointerDepth; i++ {
		assert.That(msg.CanAddr())
		msg = msg.Addr()
	}

	assert.Equal(msg.Type(), t)

	return msg.Interface(), nil
}
Beispiel #3
0
func (self *ServiceMap) ReplaceConfigs(configs collections.Sequence, fnTombstone func(string) interface{}) {
	keys := make(map[string]bool)
	for _, key := range self.keys() {
		keys[key] = true
	}

	for iterator := configs.Iterator(); iterator.HasNext(); {
		config := iterator.Next()
		key := self.keyFunction(config)

		keys[key] = false

		slot := self.getSlot(config)
		if slot == nil {
			continue
		}

		slot.channel <- config
	}

	for key, v := range keys {
		if !v {
			continue
		}

		tombstone := fnTombstone(key)

		assert.That(key == self.keyFunction(tombstone))

		self.remove(tombstone)
	}
}
Beispiel #4
0
func (self *Instance) UpdateScalingPolicy(updatePolicy *model.ScalingPolicy) (*model.ScalingPolicy, error) {
	policy, err := self.getScalingPolicy()
	if err != nil {
		return nil, err
	}

	assert.That(updatePolicy != nil)
	if updatePolicy.MetricMin != nil {
		policy.MetricMin = updatePolicy.MetricMin
	}
	if updatePolicy.MetricMax != nil {
		policy.MetricMax = updatePolicy.MetricMax
	}
	if updatePolicy.ScaleMin != nil {
		policy.ScaleMin = updatePolicy.ScaleMin
	}
	if updatePolicy.ScaleMax != nil {
		policy.ScaleMax = updatePolicy.ScaleMax
	}
	if updatePolicy.MetricName != nil {
		policy.MetricName = updatePolicy.MetricName
	}
	if updatePolicy.Window != nil {
		policy.Window = updatePolicy.Window
	}
	return self.setScalingPolicy(policy)
}
Beispiel #5
0
func (self *baseBundleType) GetCloudFoundryPlans() ([]*bundle.CloudFoundryPlan, error) {
	assert.That(self.bundleTemplate != nil)
	plans := self.bundleTemplate.GetCloudFoundryPlans()
	if plans == nil {
		plan := &bundle.CloudFoundryPlan{}
		plan.Key = "default"
		plans := []*bundle.CloudFoundryPlan{plan}
		return plans, nil
	}
	return plans, nil
}
Beispiel #6
0
func NewRestServer() *RestServer {
	self := &RestServer{}
	self.httpServer = &http.Server{
		Addr:        ":8080",
		ReadTimeout: 10 * time.Second,
		//WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	self.readers = []MessageBodyReader{}
	self.writers = []MessageBodyWriter{}

	var err error
	self.defaultMediaType, err = ParseMediaType("application/json")
	assert.That(err == nil)

	return self
}
Beispiel #7
0
func (self *baseBundleType) Init() error {
	assert.That(self.bundleTemplate != nil)

	meta := self.bundleTemplate.GetMeta()
	if meta != nil {
		self.meta = *meta
	}

	if self.meta.ReadyProperty == "" {
		self.meta.ReadyProperty = "password"
	}

	if self.meta.PrimaryRelationKey == "" {
		self.meta.PrimaryRelationKey = self.key
	}

	return nil
}
Beispiel #8
0
func (self *EndpointXaas) Item(key string, req *http.Request) (*EndpointTenant, error) {
	child := &EndpointTenant{}

	tenantId := key
	tenantName := strings.Replace(key, "-", "", -1)

	// TODO: Implement authz
	assert.That(self.Authenticator != nil)
	authentication := self.Authenticator.Authenticate(tenantId, req)

	if authentication == nil {
		log.Debug("Authentication failed")
		notAuthorized := rs.HttpError(http.StatusUnauthorized)
		notAuthorized.Headers["WWW-Authenticate"] = "Basic realm=\"jxaas\""
		return nil, notAuthorized
	} else {
		child.Tenant = tenantName
		// TODO: Use tenantId? authorization.TenantId

		return child, nil
	}
}
Beispiel #9
0
// Assigns a public port to the serviceId
func (self *Huddle) assignPublicPort(serviceId string) (int, bool, error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	var port int

	port, found := self.assignedPublicPorts[serviceId]
	if found {
		return port, false, nil
	}

	// TODO: Filter?
	prefix := ""
	statuses, err := self.JujuClient.GetServiceStatusList(prefix)
	if err != nil {
		return 0, false, err
	}

	publicPorts := []int{}

	for _, publicPort := range self.assignedPublicPorts {
		publicPorts = append(publicPorts, publicPort)
	}

	for key, _ := range statuses {
		var publicPort int

		publicPort, found := self.assignedPublicPorts[key]
		if found {
			assert.That(contains(publicPorts, publicPort))
			continue
		}

		log.Debug("Looking for public port annotation on: %v", key)

		annotations, err := self.JujuClient.GetServiceAnnotations(key)
		if err != nil {
			return 0, false, err
		}

		publicPortString := annotations[ANNOTATION_KEY_PUBLIC_PORT]
		publicPortString = strings.TrimSpace(publicPortString)
		if publicPortString == "" {
			continue
		}
		publicPort, err = strconv.Atoi(publicPortString)
		if err != nil {
			log.Warn("Error parsing public port on %v: %v", key, publicPortString, err)
		}
		self.assignedPublicPorts[key] = publicPort

		publicPorts = append(publicPorts, publicPort)
	}

	// This approach breaks down if the ports are densely assigned
	if len(publicPorts) > 9000 {
		return 0, false, fmt.Errorf("Too many ports already assigned")
	}

	for {
		port = 10000 + rand.Intn(10000)
		if contains(publicPorts, port) {
			continue
		}

		log.Debug("Public ports already assigned: %v", publicPorts)
		log.Info("Assigned port: %v", port)
		break
	}

	// We can't set the port yet; the service likely doesn't yet exist
	//	err = self.Instance.setPublicPort(port)
	//	if err != nil {
	//		return 0, err
	//	}

	// Instead we set the port in the map; this map is how we avoid double allocations before
	// we've created the service
	self.assignedPublicPorts[serviceId] = port

	return port, true, nil
}
Beispiel #10
0
// Runs a scaling query and/or change on the instance
func (self *Instance) RunScaling(changeScale bool) (*model.Scaling, error) {
	health := &model.Scaling{}

	instanceState, err := self.GetState()
	if err != nil {
		log.Warn("Error getting instance state", err)
		return nil, err
	}

	assert.That(instanceState.NumberUnits != nil)
	scaleCurrent := *instanceState.NumberUnits
	health.ScaleCurrent = scaleCurrent
	health.ScaleTarget = scaleCurrent

	policy, err := self.getScalingPolicy()
	if err != nil {
		log.Warn("Error fetching scaling policy", err)
		return nil, err
	}

	health.Policy = *policy

	var scaleTarget int

	if policy.MetricName != nil {
		// TODO: Filter by time window
		metricData, err := self.GetMetricValues(*policy.MetricName)
		if err != nil {
			log.Warn("Error retrieving metrics for scaling", err)
			return nil, err
		}

		window := 300
		if policy.Window != nil {
			window = *policy.Window
		}
		duration := time.Duration(-window) * time.Second

		now := time.Now()
		maxTime := now.Unix()
		minTime := now.Add(duration).Unix()

		matches := &model.MetricDataset{}
		for _, point := range metricData.Points {
			t := point.T

			if t < minTime {
				continue
			}

			if t > maxTime {
				continue
			}

			matches.Points = append(matches.Points, point)
		}

		matches.SortPointsByTime()

		lastTime := minTime
		var total float64
		for _, point := range matches.Points {
			t := point.T

			assert.That(t >= lastTime)

			total += float64(float32(t-lastTime) * point.V)

			lastTime = t
		}

		metricCurrent := float32(total / float64(lastTime-minTime))
		log.Info("Average of metric: %v", metricCurrent)

		health.MetricCurrent = metricCurrent

		// TODO: Smart 'target-based' scaling
		scaleDelta := 0
		if policy.MetricMin != nil && metricCurrent < *policy.MetricMin {
			scaleDelta = -1
		} else if policy.MetricMax != nil && metricCurrent > *policy.MetricMax {
			scaleDelta = +1
		}

		scaleTarget = scaleCurrent + scaleDelta
	} else {
		scaleTarget = scaleCurrent
	}

	if policy.ScaleMax != nil && scaleTarget > *policy.ScaleMax {
		scaleTarget = *policy.ScaleMax
	} else if policy.ScaleMin != nil && scaleTarget < *policy.ScaleMin {
		scaleTarget = *policy.ScaleMin
	}

	health.ScaleTarget = scaleTarget

	if changeScale && health.ScaleTarget != scaleCurrent {
		log.Info("Changing scale from %v to %v for %v", scaleCurrent, health.ScaleTarget, self)

		rescale := &model.Instance{}
		rescale.NumberUnits = new(int)
		*rescale.NumberUnits = health.ScaleTarget

		err := self.Configure(rescale)
		if err != nil {
			log.Warn("Error changing scale", err)
			return nil, err
		}
	}

	if math.IsNaN(float64(health.MetricCurrent)) {
		// Avoid error when golang tries to convert NaN to JSON (!)
		health.MetricCurrent = 0
	}

	return health, nil
}
Beispiel #11
0
// Returns the current state of the instance
func (self *Instance) getState0() (*instanceState, error) {
	jujuClient := self.GetJujuClient()

	primaryServiceId := self.primaryServiceId
	status, err := jujuClient.GetServiceStatus(primaryServiceId)

	// TODO: check err?

	jujuService, err := jujuClient.FindService(primaryServiceId)
	if err != nil {
		return nil, err
	}

	if status == nil {
		log.Warn("No state found for %v", primaryServiceId)
		return nil, nil
	}

	log.Debug("Service state: %v", status)

	state := &instanceState{}
	state.Model = model.MapToInstance(self.instanceId, status, jujuService)

	for k, v := range self.bundleType.GetDefaultOptions() {
		option, found := state.Model.OptionDescriptions[k]
		if !found {
			log.Warn("Option not found in OptionDescriptions %v in %v", k, state.Model.OptionDescriptions)
			continue
		}
		option.Default = v
		state.Model.OptionDescriptions[k] = option
	}

	state.Units = map[string]map[string]api.UnitStatus{}

	state.Units[primaryServiceId] = status.Units

	state.PublicAddresses = []string{}
	for _, unitStatus := range status.Units {
		if unitStatus.PublicAddress == "" {
			continue
		}
		state.PublicAddresses = append(state.PublicAddresses, unitStatus.PublicAddress)
	}

	serviceKeys, err := self.getBundleKeys()
	if err != nil {
		return nil, err
	}

	if serviceKeys == nil {
		return nil, rs.ErrNotFound()
	}

	// TODO: This is pretty expensive... we could just check to see if properties have been set
	for serviceId, _ := range serviceKeys {
		if serviceId == primaryServiceId {
			continue
		}

		status, err := jujuClient.GetServiceStatus(serviceId)
		if err != nil {
			log.Warn("Error while fetching status of service: %v", serviceId, err)
			state.Model.Status = "pending"
		} else if status == nil {
			log.Warn("No status for service: %v", serviceId)
			state.Model.Status = "pending"
		} else {
			log.Info("Got state of secondary service: %v => %v", serviceId, status)
			for _, unitStatus := range status.Units {
				model.MergeInstanceStatus(state.Model, &unitStatus)
			}
		}

		if status != nil {
			state.Units[serviceId] = status.Units
		}
	}

	// TODO: This is a bit of a hack also.  How should we wait for properties to be set?
	annotations, err := jujuClient.GetServiceAnnotations(primaryServiceId)
	if err != nil {
		log.Warn("Error getting annotations", err)
		// TODO: Mask error?
		return nil, err
	}

	log.Info("Annotations on %v: %v", primaryServiceId, annotations)

	state.Model.Options = map[string]string{}

	state.SystemProperties = map[string]string{}
	state.RelationMetadata = map[string]string{}

	relationList := []relationProperty{}

	for tagName, v := range annotations {
		if strings.HasPrefix(tagName, ANNOTATION_PREFIX_INSTANCECONFIG) {
			key := tagName[len(ANNOTATION_PREFIX_INSTANCECONFIG):]
			state.Model.Options[key] = v
			continue
		}

		if strings.HasPrefix(tagName, ANNOTATION_PREFIX_SYSTEM) {
			key := tagName[len(ANNOTATION_PREFIX_SYSTEM):]
			state.SystemProperties[key] = v
			continue
		}

		if strings.HasPrefix(tagName, ANNOTATION_PREFIX_RELATIONINFO) {
			suffix := tagName[len(ANNOTATION_PREFIX_RELATIONINFO):]
			tokens := strings.SplitN(suffix, "_", 3)
			if len(tokens) < 3 {
				log.Debug("Ignoring unparseable tag: %v", tagName)
				continue
			}

			unitId := tokens[0]
			relationId := tokens[1]
			key := tokens[2]
			if key[0] != '_' {
				state.RelationMetadata[key] = v
				continue
			}

			relationTokens := strings.SplitN(relationId, ":", 2)
			if len(relationTokens) != 2 {
				log.Debug("Ignoring unparseable relation id in tag: %v", tagName)
				continue
			}

			relationProperty := relationProperty{}
			relationProperty.UnitId = unitId
			assert.That(key[0] == '_')
			relationProperty.Key = key[1:]
			relationProperty.Value = v
			relationProperty.RelationType = relationTokens[0]
			relationProperty.RelationKey = relationTokens[1]
			relationList = append(relationList, relationProperty)

			continue
		}
	}

	state.Relations = map[string]map[string]string{}
	for _, relation := range relationList {
		relationType := relation.RelationType
		relations, found := state.Relations[relationType]
		if !found {
			relations = map[string]string{}
			state.Relations[relationType] = relations
		}
		relations[relation.Key] = relation.Value
	}

	// TODO: Only if otherwise ready?
	annotationsReady := self.bundleType.IsStarted(state.Relations)

	// For a subordinate charm service (e.g. multimysql), we just watch for the annotation
	if annotationsReady && state.Model.Status == "" && len(status.SubordinateTo) != 0 {
		log.Info("Subordinate instance started (per annotations): %v", self)
		state.Model.Status = "started"
	}

	if !annotationsReady {
		log.Info("Instance not started (per annotations): %v", state.Relations)
		state.Model.Status = "pending"
	}

	log.Info("Status of %v: %v", primaryServiceId, state.Model.Status)

	// TODO: Fetch inherited properties from primary service and merge

	return state, nil
}
Beispiel #12
0
func (self *RestEndpointHandler) httpHandler(res http.ResponseWriter, req *http.Request) {
	requestUrl := req.URL
	requestMethod := req.Method

	log.Debug("%v %v", requestMethod, requestUrl)

	endpoint, err := self.resolveEndpoint(req)

	if endpoint == nil && err == nil {
		err = HttpError(http.StatusNotFound)
	}

	var method reflect.Value

	if err == nil {
		httpMethod := req.Method
		methodName := "Http" + httpMethod[0:1] + strings.ToLower(httpMethod[1:])

		method = endpoint.MethodByName(methodName)
		if !method.IsValid() {
			log.Debug("Method not found: %v on %v", methodName, endpoint.Type())

			err = HttpError(http.StatusNotFound)
		}
	}

	var args []reflect.Value
	if err == nil {
		args, err = self.buildArgs(req, &method)
	}

	var val reflect.Value

	if err == nil {
		var out []reflect.Value
		out = method.Call(args)
		//		fmt.Fprintf(w, "Returned %v", out)

		val, err = parseReturn(out)
	}

	if err == nil {
		if val.IsNil() {
			err = HttpError(http.StatusNotFound)
		}
	}

	var response *HttpResponse
	var mbw MessageBodyWriter

	if err == nil {
		response, err = self.makeResponse(val)
	}
	if err == nil {
		assert.That(response != nil)

		if response.Headers == nil {
			response.Headers = make(map[string]string)
		}

		if response.Content == nil {
			mbw = &NoResponseMessageBodyWriter{}
		} else {
			contentType := response.Headers["content-type"]
			if contentType == "" {
				contentType = "application/json; charset=utf-8"
				response.Headers["content-type"] = contentType
			}

			var mediaType *MediaType
			if contentType != "" {
				mediaType, err = ParseMediaType(contentType)
			}

			if err == nil {
				assert.That(mediaType != nil)

				mbw = self.server.findMessageBodyWriter(response.Content, req, mediaType)
				if mbw == nil {
					log.Warn("Unable to find media type: %v", contentType)
					err = HttpError(http.StatusUnsupportedMediaType)
				}
			}
		}
	}

	if err == nil {
		assert.That(response != nil)
		assert.That(mbw != nil)

		log.Info("%v %v %v", response.Status, requestMethod, requestUrl)

		if response.Headers != nil {
			for name, value := range response.Headers {
				res.Header().Set(name, value)
			}
		}

		res.WriteHeader(response.Status)

		err = mbw.Write(response.Content, reflect.TypeOf(response.Content), req, res)
		if err != nil {
			log.Warn("Error while writing message body", err)
		}
	} else {
		httpError, ok := err.(*HttpErrorObject)
		if !ok {
			log.Warn("Internal error serving request", err)
			httpError = HttpError(http.StatusInternalServerError)
		}

		status := httpError.Status
		message := httpError.Message
		if message == "" {
			message = http.StatusText(status)
			if message == "" {
				message = "Error"
			}
		}

		for k, v := range httpError.Headers {
			res.Header().Add(k, v)
		}

		log.Info("%v %v %v", status, requestMethod, requestUrl)

		http.Error(res, message, status)
	}
}
Beispiel #13
0
func (self *EndpointServiceInstance) HttpPut(request *CfCreateInstanceRequest) (*rs.HttpResponse, error) {
	service := self.getService()

	log.Info("CF instance put request: %v", request)

	planId := request.PlanId
	cfServiceId := request.ServiceId

	if cfServiceId != service.CfServiceId {
		log.Warn("Service mismatch: %v vs %v", cfServiceId, service.CfServiceId)
		return nil, rs.ErrNotFound()
	}

	bundleType, instance := service.getInstance(self.Id)
	if instance == nil || bundleType == nil {
		return nil, rs.ErrNotFound()
	}

	cfPlans, err := bundleType.GetCloudFoundryPlans()
	if err != nil {
		log.Warn("Error retrieving CloudFoundry plans for bundle %v", bundleType, err)
		return nil, err
	}

	var foundPlan *bundle.CloudFoundryPlan
	for _, cfPlan := range cfPlans {
		cfPlanId := service.CfServiceId + "::" + cfPlan.Key
		if cfPlanId == planId {
			assert.That(foundPlan == nil)
			foundPlan = cfPlan
		}
	}

	if foundPlan == nil {
		log.Warn("Plan not found %v", planId)
		return nil, rs.ErrNotFound()
	}

	log.Debug("Found CF plan: %v", foundPlan)

	configureRequest := &model.Instance{}
	configureRequest.Options = foundPlan.Options

	err = instance.Configure(configureRequest)
	if err != nil {
		return nil, err
	}

	response := &CfCreateInstanceResponse{}
	// TODO: We need a dashboard URL - maybe a Juju GUI?
	response.DashboardUrl = "http://localhost:8080"
	response.State = CF_STATE_IN_PROGRESS
	response.LastOperation = &CfOperation{}
	response.LastOperation.State = CF_STATE_IN_PROGRESS

	log.Info("Sending response to CF service create", log.AsJson(response))

	httpResponse := &rs.HttpResponse{Status: http.StatusAccepted}
	httpResponse.Content = response
	return httpResponse, nil
}