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 }
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 }
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) } }
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) }
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 }
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 }
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 }
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 } }
// 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 }
// 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 }
// 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 }
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) } }
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 }